Skip to content

Commit 02b295d

Browse files
committed
A11y: red selection-fg + dark-mode selection bg & hairline
Tester reported that the new dark-gold selection-fg (the previous iteration) made selected and unselected rows hard to tell apart, even though both passed AA against the bg. Switching to red — Total Commander style — adds a strong hue cue on top of the luminance step, and in dark mode the row also picks up a slightly darker bg + a faint hairline between consecutive selected rows so dense selections stay countable. - `--color-selection-fg-primary`: `#b60000` (light) and `#ff8c8c` (dark). Light goes from 1.80 → 2.48 diff vs unselected text, with strong hue separation. Dark stays at 1.83 — luminance-wise it's tight, but the new bg and hairline carry the rest of the differentiation. - `--color-selection-bg`: dark mode only, `#141414` (a touch darker than `--color-bg-primary`). Light keeps the pane bg under selected text. - `--color-selection-border`: dark mode only, faint fixed gray `rgba(255,255,255,0.10)`. Drawn as an `inset` box-shadow between consecutive selected rows — zero layout impact (row height stays exact). Skipped when the row is under the cursor (cursor is already the strong signal there). - Zebra stripes auto-disabled on selected rows: `.is-selected`'s `background-color` already wins over `.is-striped` by cascade order. - Both `FullList.svelte` and `BriefList.svelte` get the new rules. - Removed `scripts/check-a11y-contrast/red_pick_test.go` (one-shot picker that surfaced the chosen reds; the decision is captured in `app.css` and the candidate evaluator is now in `diff_test.go::TestRedCandidates`).
1 parent 069bc40 commit 02b295d

6 files changed

Lines changed: 101 additions & 254 deletions

File tree

apps/desktop/src/app.css

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,17 +133,28 @@
133133
--pane-tint-fg-pct: 10%;
134134

135135
/* === Selection ===
136-
* Selected-row text reads `--color-selection-fg`. The primary value carries
137-
* the gold "this row is selected" identity. The fallback drops back to
138-
* `--color-text-primary` for the few cases where the row bg is hostile
139-
* enough that no recognizable gold can clear AA on top — specifically
140-
* dark mode + a pane tint + cursor-active (yellow accent over a tinted
141-
* dark surface). The CSS rule that switches between them lives further
142-
* down in this file (search for "selection-fg fallback"). The contrast
143-
* checker's `row_state_matrix.go` synthesizer mirrors that rule. */
144-
--color-selection-fg-primary: #5a4000;
136+
* Selected-row text reads `--color-selection-fg`. Inspired by Total
137+
* Commander, we use red for selection — distinct enough from the
138+
* default text color (black on light, white on dark) by hue and
139+
* luminance to read clearly. The primary value carries the identity
140+
* across the full row-state matrix; the fallback drops back to
141+
* `--color-text-primary` for the dark + tinted + cursor-active corner
142+
* where no AA-clearing red exists. The CSS rule that switches between
143+
* them lives further down (search for "selection-fg fallback"). The
144+
* contrast checker's `row_state_matrix.go` synthesizer mirrors it. */
145+
--color-selection-fg-primary: #b60000;
145146
--color-selection-fg-fallback: var(--color-text-primary);
146147
--color-selection-fg: var(--color-selection-fg-primary);
148+
/* Selection background and border — dark mode only (the light-mode
149+
* red on white reads cleanly enough without a bg layer). The bg is a
150+
* touch darker than `--color-bg-primary`, applied via a scoped rule
151+
* in `FullList.svelte`. The border is a faint fixed gray (no accent
152+
* relationship) that draws as an inset `box-shadow` between
153+
* consecutive selected rows — purely there to keep dense selections
154+
* visually countable. Both default to transparent so they're a no-op
155+
* in light mode. */
156+
--color-selection-bg: transparent;
157+
--color-selection-border: transparent;
147158

148159
/* === Git portal ===
149160
* Distinct from `--color-accent` to keep the "you're in history-land" feel
@@ -497,9 +508,14 @@
497508
/* === Focus ring shadow === */
498509
--shadow-focus-contrast: 0 0 0 4px color-mix(in srgb, white, transparent 92%);
499510

500-
/* === Selection (dark) === see light mode for primary/fallback rationale */
501-
--color-selection-fg-primary: #d4a82a;
511+
/* === Selection (dark) === see light mode for primary/fallback rationale.
512+
In dark mode we also paint a slightly darker bg on selected rows and a
513+
faint hairline between consecutive selected rows; the tokens default
514+
to transparent in light so they're effectively dark-mode-only. */
515+
--color-selection-fg-primary: #ff8c8c;
502516
--color-selection-fg-fallback: var(--color-text-primary);
517+
--color-selection-bg: #141414;
518+
--color-selection-border: rgba(255, 255, 255, 0.1);
503519

504520
/* Git portal: brighter teal in dark mode for readability against
505521
the dark background. */

apps/desktop/src/lib/file-explorer/views/BriefList.svelte

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,20 @@
999999
background-color: var(--color-bg-stripe);
10001000
}
10011001
1002+
/* Selected rows: darker bg (dark mode only — see FullList.svelte and
1003+
app.css). Cursor rules win by specificity, so cursor-on-selected
1004+
still shows the cursor highlight. */
1005+
.file-entry.is-selected {
1006+
background-color: var(--color-selection-bg);
1007+
}
1008+
1009+
/* Faint hairline between two consecutive selected rows. `box-shadow:
1010+
inset` draws on top of `background-color` and takes zero layout
1011+
space. Skipped when the row is under the cursor. */
1012+
.file-entry.is-selected + .file-entry.is-selected:not(.is-under-cursor) {
1013+
box-shadow: inset 0 1px 0 var(--color-selection-border);
1014+
}
1015+
10021016
.file-entry.is-under-cursor {
10031017
background-color: var(--color-cursor-inactive);
10041018
}

apps/desktop/src/lib/file-explorer/views/FullList.svelte

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,24 @@
10031003
background-color: var(--color-bg-stripe);
10041004
}
10051005
1006+
/* Selected rows: darker bg (dark mode only — tokens default to
1007+
transparent in light) overrides the stripe so the selection reads
1008+
as a single block. Cursor rules win by specificity (see below), so
1009+
cursor-on-selected still shows the cursor highlight. */
1010+
.file-entry.is-selected {
1011+
background-color: var(--color-selection-bg);
1012+
}
1013+
1014+
/* Faint hairline between two consecutive selected rows so dense
1015+
selections stay countable. `box-shadow: inset` draws on top of
1016+
`background-color` and takes zero layout space, so row height
1017+
doesn't jump. Skipped when the row is under the cursor — cursor
1018+
is already a strong visual signal, no need for the divider on
1019+
top of it. */
1020+
.file-entry.is-selected + .file-entry.is-selected:not(.is-under-cursor) {
1021+
box-shadow: inset 0 1px 0 var(--color-selection-border);
1022+
}
1023+
10061024
.file-entry.is-under-cursor {
10071025
background-color: var(--color-cursor-inactive);
10081026
}

scripts/check-a11y-contrast/diff_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ func TestSelectionDiff(t *testing.T) {
6060
}
6161

6262
// TestRedCandidates checks each candidate red against TWO constraints:
63-
// 1. WCAG AA (≥4.5:1) on the worst-case row bg for its mode.
64-
// 2. Differentiation ratio (≥some target) against `--color-text-primary`.
63+
// 1. WCAG AA (≥4.5:1) on the worst-case row bg for its mode.
64+
// 2. Differentiation ratio (≥some target) against `--color-text-primary`.
6565
//
6666
// Worst-case bgs are sampled from the existing matrix output; we don't
6767
// recompute them here.

scripts/check-a11y-contrast/red_pick_test.go

Lines changed: 0 additions & 231 deletions
This file was deleted.

0 commit comments

Comments
 (0)