From a31b50c5cb350fb27c49d1b30f8c07314e13d8f8 Mon Sep 17 00:00:00 2001 From: Palash Debnath Date: Wed, 9 Sep 2026 21:06:19 -0700 Subject: [PATCH] fix(a11y): land the light theme, with one token raised to clear WCAG AA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1973. Lands #1975 by @CoDe-ReDz. The app shipped six themes, all dark, and "auto" stayed dark on a light-mode OS — so a user looking for a light mode found nothing. Light text on a dark background causes halation for people with astigmatism, which makes this an accessibility gap rather than a preference. One correction to the contributor's palette: --chrome-fg-muted at #586e75 gives 4.39:1 against --chrome-bg #eee8d5, just under the 4.5:1 AA threshold for normal text. Raised to #4d5f66 (5.45:1) in both the explicit light block and the prefers-color-scheme mirror, keeping it in the Solarized family. For the record, the two contrast failures the review bot flagged as P1 are not real: --color-fg-subtle measures 6.66:1 and --chrome-fg-dim 5.86:1, both comfortably AA. The token that actually failed was one it did not mention. Everything else the bots raised was already handled on the branch: both theme labels go through t() with real translations in all 21 locales, and the header's white-to-grey gradient is overridden for the explicit light theme and the auto mirror alike. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S --- CHANGELOG.md | 1 + frontend/src/components/settings/AppearancePanel.jsx | 11 +++++++++-- .../src/components/settings/AppearancePanel.test.jsx | 8 ++++---- frontend/src/index.css | 4 ++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72583807..6ebb7c4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ the frozen-backend fallback mirror it for their toolchains. ## [Unreleased] **Highlights** +- A light theme, and System Auto now follows a light-mode OS instead of staying dark (#1973) — thanks @CoDe-ReDz! - Generating from a one-character input now says the input was too short, instead of quoting a convolution error (#1826) - First run asks about text size before the install, not after it (#1849) - Cloning without a reference clip now says so, instead of naming library parameters you cannot set (#1879) diff --git a/frontend/src/components/settings/AppearancePanel.jsx b/frontend/src/components/settings/AppearancePanel.jsx index 91cce78e..f46ea6fe 100644 --- a/frontend/src/components/settings/AppearancePanel.jsx +++ b/frontend/src/components/settings/AppearancePanel.jsx @@ -13,7 +13,12 @@ import { useAppStore, FONT_OPTIONS, FONT_STACKS } from '../../store'; import { SettingsSection, SettingRow, InfoHint, SettingsToggle } from './primitives'; const THEMES = [ - { id: 'auto', labelKey: 'settings.theme_auto', defaultLabel: 'System Auto', dot: 'linear-gradient(135deg, #fdf6e3 50%, #1d2021 50%)' }, + { + id: 'auto', + labelKey: 'settings.theme_auto', + defaultLabel: 'System Auto', + dot: 'linear-gradient(135deg, #fdf6e3 50%, #1d2021 50%)', + }, { id: 'gruvbox', label: 'Gruvbox', dot: '#d3869b' }, { id: 'midnight', label: 'Midnight', dot: '#8b5cf6' }, { id: 'nord', label: 'Nord', dot: '#88c0d0' }, @@ -205,7 +210,9 @@ export default function AppearancePanel() { onClick={() => setTheme(th.id)} onKeyDown={(e) => radioGroupKeyDown(e, themeIds, theme, setTheme)} title={th.labelKey ? t(th.labelKey, { defaultValue: th.defaultLabel }) : th.label} - aria-label={th.labelKey ? t(th.labelKey, { defaultValue: th.defaultLabel }) : th.label} + aria-label={ + th.labelKey ? t(th.labelKey, { defaultValue: th.defaultLabel }) : th.label + } aria-checked={theme === th.id} role="radio" tabIndex={radioTabIndex(themeIds, theme, th.id)} diff --git a/frontend/src/components/settings/AppearancePanel.test.jsx b/frontend/src/components/settings/AppearancePanel.test.jsx index eff43d67..02807f4e 100644 --- a/frontend/src/components/settings/AppearancePanel.test.jsx +++ b/frontend/src/components/settings/AppearancePanel.test.jsx @@ -153,17 +153,17 @@ describe('AppearancePanel — navigation style picker', () => { describe('AppearancePanel — theme selection', () => { it('renders localized labels for the Auto and Light themes', () => { render(); - + // The test runner uses the `defaultValue` from our t() hooks. // This verifies the labels are correctly exposed to screen readers via aria-label. const autoTheme = screen.getByRole('radio', { name: 'System Auto' }); const lightTheme = screen.getByRole('radio', { name: 'Light' }); - + expect(autoTheme).toBeInTheDocument(); expect(lightTheme).toBeInTheDocument(); - + // Verify it generates the correct data attribute for the OS-sync theme expect(autoTheme).toHaveAttribute('data-radio-value', 'auto'); expect(lightTheme).toHaveAttribute('data-radio-value', 'light'); }); -}); \ No newline at end of file +}); diff --git a/frontend/src/index.css b/frontend/src/index.css index c3136d6e..4f7afcc9 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -591,7 +591,7 @@ --chrome-bg: #eee8d5; --chrome-fg: #073642; - --chrome-fg-muted: #586e75; + --chrome-fg-muted: #4d5f66; --chrome-fg-dim: #4a5a60; --chrome-border: #d5ccb7; @@ -629,7 +629,7 @@ --chrome-bg: #eee8d5; --chrome-fg: #073642; - --chrome-fg-muted: #586e75; + --chrome-fg-muted: #4d5f66; --chrome-fg-dim: #4a5a60; --chrome-border: #d5ccb7;