Overview
src/lib/theme.ts defines a complete semanticTokens.colors set (app.bg, app.surface, app.text, app.accent, etc.) with explicit default/_dark pairs, including a code comment documenting a deliberate contrast audit: "app.accent": { default: "#0f7a4e", _dark: "#4ae292" }, // Accent — #0f7a4e passes 4.5:1 on white; #4ae292 is the dark-mode green. This is the mechanism #44 (closed, "Add WCAG-compliant dark/light theme toggle with colour-contrast audit on all text elements") established. There is, however, no semantic token for error or warning states anywhere in theme.ts — no app.error, app.errorBg, app.warning, app.warningBg. Every error/warning Alert across the deposit and unlock flows instead hardcodes literal, dark-mode-only hex values directly in JSX, completely bypassing the token system:
// src/app/farm/page.tsx — DepositModal, three separate hardcoded pairs
<Alert bg="#2a1414" color="#ff8080">...</Alert> {/* error */}
<Alert bg="#2d2216" color="#ffb86c" border="1px solid #7c5c24">...</Alert> {/* fee-sponsored warning */}
<Alert bg="#2a2412" color="#f6c453">...</Alert> {/* not-connected warning */}
// src/components/UnlockModal/UnlockModal.tsx — same pattern repeated
<Alert bg="#2a2412" color="#f6c453">...</Alert> {/* lock-period warning */}
<Alert bg="#2a1414" color="#ff8080">...</Alert> {/* error */}
theme.ts's initialColorMode: "dark" is only the default — ThemeToggle.tsx (useColorMode().toggleColorMode) lets any user switch to light mode, persisted via colorModeManager={localStorageManager} (context/index.tsx:35), and every other color in the app correctly re-themes because it references an app.* token. These hardcoded alert colors do not: #2a1414 is a near-black, low-luminance dark red designed to sit against a dark surface with #ff8080 (a light salmon) as legible text on top of it. Once a user switches to light mode, this exact same near-black box with light-pink text renders unchanged — a jarring, un-themed dark patch inside an otherwise white/light-gray page, with no guarantee the resulting contrast ratio (dark-red-on-white-page-adjacent, light-pink-text-on-dark-red-box) was ever audited the way app.accent's comment documents #0f7a4e/#4ae292 were.
Requirements
- Add semantic tokens to
theme.ts for error and warning states (e.g. app.errorBg/app.errorFg, app.warningBg/app.warningFg), each with a real, documented default/_dark pair audited for contrast the same way the existing accent/text tokens were.
- Replace every hardcoded hex literal in
DepositModal (farm/page.tsx), UnlockModal.tsx, and PoolDetailClient.tsx's error/warning Alerts with the new semantic tokens.
- Verify visually (or via an automated contrast-ratio check) that both light and dark mode meet WCAG AA (4.5:1) for these alert boxes, matching the rigor of the existing accent-color comment.
Acceptance Criteria
Additional Notes
More precise references
src/lib/theme.ts:14-34 (semanticTokens.colors) — confirmed the full token list and confirmed no error/warning-flavored token exists anywhere in it.
src/lib/theme.ts:24 — confirmed the exact contrast-audit comment style ("#0f7a4e passes 4.5:1 on white") that error/warning tokens should match once added.
src/app/farm/page.tsx:355-360 (amountValid error text, color="#ff8080"), :362-367 (exceedsBalance, same), :369-374 (localError, same), :376-381 (isFeeSponsored, bg="#2d2216" color="#ffb86c" border="1px solid #7c5c24"), :383-388 (not-connected, bg="#2a2412" color="#f6c453") — confirmed five separate hardcoded-color Alerts in this one component alone.
src/components/UnlockModal/UnlockModal.tsx:312-323 (lock-period warning, bg="#2a2412" color="#f6c453"), :412-424 (timeout warning, same pair), :426-440 (min-deposit warning, same pair again), :442-453 (error, bg="#2a1414" color="#ff8080") — confirmed the identical color pairs are reused across at least four separate alert instances in this file too, consistent with the pattern being systemic rather than a one-off typo.
src/lib/theme.ts:3-6 (config.initialColorMode: "dark", useSystemColorMode: false) and src/components/ThemeToggle/ThemeToggle.tsx:27-30 (useColorMode().toggleColorMode) — confirmed light mode is a real, user-reachable state, not merely a theoretical config option.
src/context/index.tsx:35 (colorModeManager={localStorageManager}) — confirmed a user's light-mode choice persists across sessions, so this isn't a transient state a user would rarely encounter.
Additional edge cases
- The exact same
#2a2412/#f6c453 pair (warning) and #2a1414/#ff8080 pair (error) recur verbatim across both files, confirming these were likely copy-pasted from one original, dark-mode-only design rather than independently chosen — a single pair of new semantic tokens can replace all of them in one pass.
PoolDetailClient.tsx's deposit modal reuses the same error-color pattern for its own error Alert (flow.step === "error" branch) — confirm it's included in the same sweep, not just the two files explicitly quoted above.
- Because these hardcoded colors currently happen to look acceptable in the app's default dark mode, this bug has zero visible symptoms for the overwhelming majority of users/reviewers who never toggle to light mode — worth explicitly testing in light mode as part of any manual QA pass for this fix, not just relying on the default view.
Implementation sketch
// theme.ts, added to semanticTokens.colors:
"app.errorBg": { default: "#fdecec", _dark: "#2a1414" },
"app.errorFg": { default: "#b3261e", _dark: "#ff8080" },
"app.warningBg": { default: "#fdf3e2", _dark: "#2a2412" },
"app.warningFg": { default: "#8a5a00", _dark: "#f6c453" },
// farm/page.tsx / UnlockModal.tsx, e.g.:
<Alert status="error" borderRadius="2xl" bg="app.errorBg" color="app.errorFg">
(Exact hex values above are illustrative starting points, not final — a real contrast check against both the light app.bg (#ffffff) and dark app.bg (#0b0d0c) should confirm/adjust them before merging, mirroring how app.accent's existing values were justified.)
Test/reproduction plan
- Render
DepositModal with an invalid amount (triggering the error Alert) under both ChakraProvider color modes; assert the computed background/text color differs between light and dark (proving it's token-driven, not hardcoded) and that each meets a 4.5:1 contrast check (e.g. via a small contrast-ratio utility in the test).
- Visual/manual QA: toggle
ThemeToggle while an error or warning alert is visible in UnlockModal/DepositModal; confirm it re-themes instead of staying frozen.
Cross-references
Overview
src/lib/theme.tsdefines a completesemanticTokens.colorsset (app.bg,app.surface,app.text,app.accent, etc.) with explicitdefault/_darkpairs, including a code comment documenting a deliberate contrast audit:"app.accent": { default: "#0f7a4e", _dark: "#4ae292" }, // Accent — #0f7a4e passes 4.5:1 on white; #4ae292 is the dark-mode green. This is the mechanism #44 (closed, "Add WCAG-compliant dark/light theme toggle with colour-contrast audit on all text elements") established. There is, however, no semantic token for error or warning states anywhere intheme.ts— noapp.error,app.errorBg,app.warning,app.warningBg. Every error/warningAlertacross the deposit and unlock flows instead hardcodes literal, dark-mode-only hex values directly in JSX, completely bypassing the token system:theme.ts'sinitialColorMode: "dark"is only the default —ThemeToggle.tsx(useColorMode().toggleColorMode) lets any user switch to light mode, persisted viacolorModeManager={localStorageManager}(context/index.tsx:35), and every other color in the app correctly re-themes because it references anapp.*token. These hardcoded alert colors do not:#2a1414is a near-black, low-luminance dark red designed to sit against a dark surface with#ff8080(a light salmon) as legible text on top of it. Once a user switches to light mode, this exact same near-black box with light-pink text renders unchanged — a jarring, un-themed dark patch inside an otherwise white/light-gray page, with no guarantee the resulting contrast ratio (dark-red-on-white-page-adjacent, light-pink-text-on-dark-red-box) was ever audited the wayapp.accent's comment documents#0f7a4e/#4ae292were.Requirements
theme.tsfor error and warning states (e.g.app.errorBg/app.errorFg,app.warningBg/app.warningFg), each with a real, documenteddefault/_darkpair audited for contrast the same way the existing accent/text tokens were.DepositModal(farm/page.tsx),UnlockModal.tsx, andPoolDetailClient.tsx's error/warningAlerts with the new semantic tokens.Acceptance Criteria
theme.tsdefinesapp.errorBg/app.errorFgandapp.warningBg/app.warningFg(naming flexible) with bothdefaultand_darkvalues.src/app/farm,src/app/farm/[poolId], orsrc/components/UnlockModalhardcodes a literal hex color for an error/warningAlert'sbg/color/borderprops — all reference the new tokens.ThemeTogglechanges the rendered background/text color of these alerts (verifiable via a snapshot or computed-style test across both color modes).app.accent's comment documents its own ratio.Additional Notes
More precise references
src/lib/theme.ts:14-34(semanticTokens.colors) — confirmed the full token list and confirmed no error/warning-flavored token exists anywhere in it.src/lib/theme.ts:24— confirmed the exact contrast-audit comment style ("#0f7a4e passes 4.5:1 on white") that error/warning tokens should match once added.src/app/farm/page.tsx:355-360(amountValiderror text,color="#ff8080"),:362-367(exceedsBalance, same),:369-374(localError, same),:376-381(isFeeSponsored,bg="#2d2216" color="#ffb86c" border="1px solid #7c5c24"),:383-388(not-connected,bg="#2a2412" color="#f6c453") — confirmed five separate hardcoded-colorAlerts in this one component alone.src/components/UnlockModal/UnlockModal.tsx:312-323(lock-period warning,bg="#2a2412" color="#f6c453"),:412-424(timeout warning, same pair),:426-440(min-deposit warning, same pair again),:442-453(error,bg="#2a1414" color="#ff8080") — confirmed the identical color pairs are reused across at least four separate alert instances in this file too, consistent with the pattern being systemic rather than a one-off typo.src/lib/theme.ts:3-6(config.initialColorMode: "dark", useSystemColorMode: false) andsrc/components/ThemeToggle/ThemeToggle.tsx:27-30(useColorMode().toggleColorMode) — confirmed light mode is a real, user-reachable state, not merely a theoretical config option.src/context/index.tsx:35(colorModeManager={localStorageManager}) — confirmed a user's light-mode choice persists across sessions, so this isn't a transient state a user would rarely encounter.Additional edge cases
#2a2412/#f6c453pair (warning) and#2a1414/#ff8080pair (error) recur verbatim across both files, confirming these were likely copy-pasted from one original, dark-mode-only design rather than independently chosen — a single pair of new semantic tokens can replace all of them in one pass.PoolDetailClient.tsx's deposit modal reuses the same error-color pattern for its own errorAlert(flow.step === "error"branch) — confirm it's included in the same sweep, not just the two files explicitly quoted above.Implementation sketch
(Exact hex values above are illustrative starting points, not final — a real contrast check against both the light
app.bg(#ffffff) and darkapp.bg(#0b0d0c) should confirm/adjust them before merging, mirroring howapp.accent's existing values were justified.)Test/reproduction plan
DepositModalwith an invalid amount (triggering the errorAlert) under bothChakraProvidercolor modes; assert the computed background/text color differs between light and dark (proving it's token-driven, not hardcoded) and that each meets a 4.5:1 contrast check (e.g. via a small contrast-ratio utility in the test).ThemeTogglewhile an error or warning alert is visible inUnlockModal/DepositModal; confirm it re-themes instead of staying frozen.Cross-references