Skip to content

Raise the contrast of the focus indicators, and of the text on selected rows in dark mode - #4289

Merged
nstepien merged 13 commits into
mainfrom
focus-indicator-contrast
Nov 11, 2026
Merged

nstepien merged 13 commits into
mainfrom
focus-indicator-contrast

Conversation

@nstepien

@nstepien nstepien commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

In light mode, the active cell's outline and the checkbox's focus ring had contrast ratios between 1.6:1 and 2.4:1 against the backgrounds they're drawn on. WCAG 2.2's Understanding 1.4.11 Non-text Contrast applies its 3:1 minimum to focus indicators, and 2.4.13 Focus Appearance (AAA) asks for 3:1 between the focused and unfocused states. In dark mode, both were below 3:1 on selected rows only. The default text color on selected rows was also only 3.65:1 there, below the 4.5:1 of 1.4.3 Contrast (Minimum).

Light mode gets a darker outline and focus ring. Dark mode gets darker selected rows instead, which fixes the text and lets the outline and focus ring keep their current colors.

- --rdg-selection-color: hsl(207, 75%, 66%);
+ --rdg-selection-color: light-dark(hsl(207deg 80% 42%), hsl(207deg 75% 66%));
- --rdg-row-selected-background-color: light-dark(hsl(207deg 76% 92%), hsl(207deg 76% 42%));
+ --rdg-row-selected-background-color: light-dark(hsl(207deg 76% 92%), hsl(207deg 76% 26%));
- --rdg-row-selected-hover-background-color: light-dark(hsl(207deg 76% 88%), hsl(207deg 76% 38%));
+ --rdg-row-selected-hover-background-color: light-dark(hsl(207deg 76% 88%), hsl(207deg 76% 22%));
- --rdg-checkbox-focus-color: hsl(207deg 100% 69%);
+ --rdg-checkbox-focus-color: light-dark(hsl(207deg 80% 42%), hsl(207deg 100% 69%));

Contrast ratios

Light mode:

Background Selection, before Checkbox focus, before Both, after
Background 2.37 2.14 4.90
Header and group rows 2.24 2.03 4.64
Hovered row 2.17 1.96 4.49
Draggable header 1.91 1.73 3.96
Selected row 1.96 1.78 4.06
Hovered selected row 1.78 1.61 3.69

Dark mode, on selected rows:

Foreground Selected row, before Selected row, after Hovered selected row, before Hovered selected row, after
Text 3.65 7.07 4.28 8.35
Selection 2.10 4.06 2.46 4.80
Checkbox focus 2.31 4.48 2.71 5.29

On the other dark backgrounds, the colors don't change, and the outline and focus ring stay at 5.85:1 or more.

The outline also covers the cell borders: against them, it reaches 3.61:1 (#ddd) in light mode and 4.12:1 (#444) in dark mode.

Selected rows stand out less from the dark background, at 1.67:1 instead of 3.24:1. In light mode they're at 1.21:1, and the checkboxes also show which rows are selected.

Changes

  • src/style/core.ts: the new values above.
    • The selection color also draws the active row's border in a TreeDataGrid, the drag handle, and the text editor's border, so they get the same contrast.
    • The forced colors values are unchanged.
    • Grids that set these variables keep their colors.
  • README: the CSS variables section lists the new defaults.
  • test/visual/forcedColors.test.tsx → test/visual/colorSchemes.test.tsx: the same five scenarios also run in the light and dark color schemes without forced colors, in all three browsers (30 new screenshots).
  • test/browser/styles.css → test/styles.css, as the browser and visual tests share it. It now sets :root { color-scheme: light dark; }, so grids follow the emulated color scheme, as the README describes for apps.
    • This also changes the dark forced colors screenshots in Chromium: its emulated dark palette resolves SelectedItem to light blue and SelectedItemText to dark gray for a dark color-scheme, instead of dark blue and white. The outline, focus ring, and drag handle stay visible on both.
    • The light screenshots, and the basicGrid and treeGrid ones, don't change.
  • Modernized syntax:
    • The header drag image's outline uses hsl(207deg 100% 50%) instead of hsl(207, 100%, 50%).
    • Logical sizes and margins instead of physical ones: in the frozen column shadow and the drag image (src/), three demos, the README's style example, and the visual tests.
    • Neither changes any screenshot.
  • The Update Screenshots workflow regenerated every screenshot. The Firefox and WebKit screenshots of basicGrid and treeGrid, and the light forced colors screenshots in Firefox, differ by a few bytes. None of them show a color this PR changes, and their Chromium screenshots are unchanged.

Verification

vp run ci --coverage passes.

The ratios in the tables come from the WCAG 2 relative luminance formula, applied to the colors in src/style/core.ts. A temporary browser test checked them against the colors the browsers actually compute, in Chromium, Firefox, and WebKit. It covered the header row and selected, unselected, and hovered rows in both color schemes:

  • With this PR's colors, every ratio passed.
  • With main's colors, the light mode focus ring and the dark selected rows failed.
  • With this PR's first version, which had brighter dark selected rows, the text on those rows failed.
  • Its ratios matched the tables to within the browsers' rounding of computed colors.

I looked at a sample of the new screenshots in each browser: the dark ones show the darker selected rows with the lighter outline, and the light ones the darker outline.

🤖 Generated with Claude Code

nstepien and others added 3 commits November 11, 2026 15:46
…ring

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tests

The default text color had a 3.66:1 contrast on selected rows in dark mode,
below WCAG's 4.5:1. Darker selected rows fix it, and let the outline and the
checkbox focus ring keep their previous colors in dark mode.

A browser test checks the contrast of the text, the active cell outline, and
the checkbox focus ring on header, selected, unselected, and hovered rows,
in both color schemes. The forced colors visual tests now also take
screenshots in the light and dark color schemes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nstepien and others added 2 commits November 11, 2026 18:32
The grid only follows the system preference with `color-scheme: light dark`,
so the light and dark screenshots were identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nstepien and others added 2 commits November 11, 2026 18:35
With `color-scheme: light dark`, Chromium's emulated forced colors palette
changed the selected rows' system colors in the dark screenshots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nstepien nstepien changed the title Raise the contrast of the active cell outline and the checkbox focus ring Raise the contrast of the focus indicators, and of the text on selected rows in dark mode Nov 11, 2026
`:root { color-scheme: light dark; }` in the shared test stylesheet makes
grids follow the emulated color scheme, instead of a style on the visual
tests' grid. The stylesheet moves to `test/`, as both the browser and the
visual tests use it.

The header drag image's outline uses the space-separated `hsl()` syntax,
and the frozen column shadow, the drag image, the demos, the README
example, and the visual tests use logical sizes and margins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nstepien
nstepien marked this pull request as ready for review November 11, 2026 20:25
nstepien and others added 2 commits November 11, 2026 20:37
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nstepien
nstepien merged commit 4e31410 into main Nov 11, 2026
6 checks passed
@nstepien
nstepien deleted the focus-indicator-contrast branch November 11, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant