fix(a11y): land the light theme, with one token raised to clear WCAG AA
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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ypcgSsh5j2PEonSJiAU1S
This commit is contained in:
co-authored by
Claude Opus 5
parent
cb4aaa1580
commit
a31b50c5cb
@@ -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)
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -153,17 +153,17 @@ describe('AppearancePanel — navigation style picker', () => {
|
||||
describe('AppearancePanel — theme selection', () => {
|
||||
it('renders localized labels for the Auto and Light themes', () => {
|
||||
render(<AppearancePanel />);
|
||||
|
||||
|
||||
// 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user