/* plain — the skeleton: greyscale, system type, almost no shape. Structure lives in tokens.css; this file is colour, type and shape. The look: nothing. plain is the theme you pick when you intend to write your own — a working, accessible, entirely unstyled-looking app that shows you the structure and none of the taste, so the first colour you add is visibly yours. There is no accent hue: the accent is near-black in light and near-white in dark, which is to say a link reads as text with an underline on hover and a primary button reads as a filled rectangle. That is deliberate. Add the hue. Status still has to signal. A skeleton that renders an error the same grey as a success is not minimal, it is broken, so the four tone pairs keep conventional hues at very low chroma — a green you would call grey-green, an ochre, a brick — each still clearing 4.5:1 on its own tint. Colour is never the only channel (the pills carry words), but it is not thrown away either. Shadows are real and minimal. tokens.css paints with var(--rst-shadow-*) unconditionally and a theme cannot opt out of a property it must declare, so "no shadows" here means one hairline layer at low alpha rather than a value the CSS parser would drop. You get an edge, not a lift. ── One block, both schemes ─────────────────────────────────────────── Every token is declared once, as light-dark(, ), under a single :root that sets color-scheme: light dark. The two rules at the bottom are the whole toggle: setting color-scheme on :root[data-theme=…] re-resolves every light-dark() in the file at once, in both directions. Tokens with one value in both schemes — the font stack, the radii — are written plain. light-dark() is a colour function: it may only stand where a is expected. So a shadow token wraps its colour in it and writes the geometry once, outside the call — `0 8px 24px light-dark(a, b)`, never `light-dark(0 8px 24px a, …)`, which no browser would accept as a box-shadow. ── WCAG 2.2 AA, measured ───────────────────────────────────────────── Normal text needs 4.5:1; large text and UI component boundaries need 3:1. Every pair below was computed from these exact hex values, in both schemes. If you change a token, recompute its rows. LIGHT ratio requirement --rst-text on --rst-surface 18.88 4.5:1 text --rst-text on --rst-bg 18.88 4.5:1 text --rst-text on --rst-surface-2 17.17 4.5:1 text --rst-text on --rst-accent-soft 16.13 4.5:1 text --rst-text-muted on --rst-surface 8.86 4.5:1 text --rst-text-muted on --rst-bg 8.86 4.5:1 text --rst-text-muted on --rst-surface-2 8.06 4.5:1 text --rst-text-muted on --rst-accent-soft 7.57 4.5:1 text --rst-text-faint on --rst-surface 6.01 4.5:1 text --rst-text-faint on --rst-bg 6.01 4.5:1 text --rst-text-faint on --rst-surface-2 5.46 4.5:1 text --rst-text-faint on --rst-accent-soft 5.13 4.5:1 text --rst-accent on --rst-surface 18.88 4.5:1 text + focus ring --rst-accent on --rst-bg 18.88 4.5:1 text + focus ring --rst-accent on --rst-surface-2 17.17 4.5:1 text --rst-accent on --rst-accent-soft 16.13 4.5:1 text --rst-on-accent on --rst-accent 18.88 4.5:1 button label --rst-on-accent on --rst-accent-strong 21.00 4.5:1 button label, hover --rst-line-strong on --rst-surface 4.54 3:1 control border --rst-line-strong on --rst-bg 4.54 3:1 control border --rst-line-strong on --rst-surface-2 4.13 3:1 control border pill neutral fg on its own tint 8.75 4.5:1 text pill positive fg on its own tint 6.46 4.5:1 text pill warning fg on its own tint 6.09 4.5:1 text pill negative fg on its own tint 7.55 4.5:1 text --rst-on-accent on negative fg (fill) 9.44 4.5:1 danger button label --rst-on-accent-fill on --rst-accent-fill 18.88 4.5:1 primary button label --rst-on-accent-fill on --rst-accent-fill-hover 21.00 4.5:1 primary button label, hover --rst-on-danger-fill on --rst-danger-fill 9.44 4.5:1 danger button label --rst-on-danger-fill-hover on --rst-danger-fill-hover 9.44 4.5:1 danger button label, hover --rst-text on --rst-row-tint-hover 17.17 4.5:1 list-row-action text, hovered --rst-text on --rst-row-tint-press 17.17 4.5:1 list-row-action text, pressed --rst-control-text-hover on --rst-control-face-hover 18.88 4.5:1 plain button label, hover --rst-text-muted on --rst-control-face-hover 8.86 4.5:1 muted label on a hovered plain face --rst-text on --rst-tint-hover 16.13 4.5:1 row text on the hover tint --rst-text on --rst-tint-press 16.13 4.5:1 row text on the press tint --rst-text-muted on --rst-tint-hover 7.57 4.5:1 muted cell on the hover tint --rst-text-muted on --rst-tint-press 7.57 4.5:1 muted cell on the press tint --rst-text-faint on --rst-tint-hover 5.13 4.5:1 faint cell on the hover tint --rst-text-faint on --rst-tint-press 5.13 4.5:1 faint cell on the press tint --rst-accent on --rst-tint-hover 16.13 4.5:1 a link on a hovered row --rst-nav-current-fg on --rst-nav-current-bg 16.13 4.5:1 the current nav item --rst-track-off on --rst-surface 4.54 3:1 switch off track (1.4.11 boundary) --rst-track-off on --rst-bg 4.54 3:1 switch off track on the page --rst-knob-shade on --rst-track-off 4.54 3:1 switch knob on the off track --rst-knob-shade on --rst-accent-fill 18.88 3:1 switch knob on the on track DARK ratio requirement --rst-text on --rst-surface 16.01 4.5:1 text --rst-text on --rst-bg 17.12 4.5:1 text --rst-text on --rst-surface-2 16.60 4.5:1 text --rst-text on --rst-accent-soft 13.87 4.5:1 text --rst-text-muted on --rst-surface 8.55 4.5:1 text --rst-text-muted on --rst-bg 9.14 4.5:1 text --rst-text-muted on --rst-surface-2 8.86 4.5:1 text --rst-text-muted on --rst-accent-soft 7.40 4.5:1 text --rst-text-faint on --rst-surface 6.86 4.5:1 text --rst-text-faint on --rst-bg 7.33 4.5:1 text --rst-text-faint on --rst-surface-2 7.11 4.5:1 text --rst-text-faint on --rst-accent-soft 5.94 4.5:1 text --rst-accent on --rst-surface 16.01 4.5:1 text + focus ring --rst-accent on --rst-bg 17.12 4.5:1 text + focus ring --rst-accent on --rst-surface-2 16.60 4.5:1 text --rst-accent on --rst-accent-soft 13.87 4.5:1 text --rst-on-accent on --rst-accent 17.12 4.5:1 button label --rst-on-accent on --rst-accent-strong 19.17 4.5:1 button label, hover --rst-line-strong on --rst-surface 5.54 3:1 control border --rst-line-strong on --rst-bg 5.93 3:1 control border --rst-line-strong on --rst-surface-2 5.75 3:1 control border pill neutral fg on its own tint 9.05 4.5:1 text pill positive fg on its own tint 8.70 4.5:1 text pill warning fg on its own tint 8.82 4.5:1 text pill negative fg on its own tint 7.63 4.5:1 text --rst-on-accent on negative fg (fill) 8.93 4.5:1 danger button label --rst-on-accent-fill on --rst-accent-fill 17.12 4.5:1 primary button label --rst-on-accent-fill on --rst-accent-fill-hover 19.17 4.5:1 primary button label, hover --rst-on-danger-fill on --rst-danger-fill 8.93 4.5:1 danger button label --rst-on-danger-fill-hover on --rst-danger-fill-hover 8.93 4.5:1 danger button label, hover --rst-text on --rst-row-tint-hover 16.60 4.5:1 list-row-action text, hovered --rst-text on --rst-row-tint-press 16.60 4.5:1 list-row-action text, pressed --rst-control-text-hover on --rst-control-face-hover 16.01 4.5:1 plain button label, hover --rst-text-muted on --rst-control-face-hover 8.55 4.5:1 muted label on a hovered plain face --rst-text on --rst-tint-hover 13.87 4.5:1 row text on the hover tint --rst-text on --rst-tint-press 13.87 4.5:1 row text on the press tint --rst-text-muted on --rst-tint-hover 7.40 4.5:1 muted cell on the hover tint --rst-text-muted on --rst-tint-press 7.40 4.5:1 muted cell on the press tint --rst-text-faint on --rst-tint-hover 5.94 4.5:1 faint cell on the hover tint --rst-text-faint on --rst-tint-press 5.94 4.5:1 faint cell on the press tint --rst-accent on --rst-tint-hover 13.87 4.5:1 a link on a hovered row --rst-nav-current-fg on --rst-nav-current-bg 13.87 4.5:1 the current nav item --rst-track-off on --rst-surface 5.54 3:1 switch off track (1.4.11 boundary) --rst-track-off on --rst-bg 5.93 3:1 switch off track on the page --rst-knob-shade on --rst-track-off 5.54 3:1 switch knob on the off track --rst-knob-shade on --rst-accent-fill 16.01 3:1 switch knob on the on track The tightest pair in the set is --rst-text-faint on the accent tint — 5.13:1 in light, 5.94:1 in dark, against a 4.5:1 floor. Everything else has room, which is what a greyscale palette buys you. --rst-line is a hairline divider, not a control border, so 1.4.11's 3:1 does not apply to it. Control borders use --rst-line-strong. --rst-header-rule is not in the table either, and deliberately so (design doc §6-v2.2): the page header's rule is decoration, not structure — the heading's size, weight and spacing carry the structure — so no contrast floor applies to it and it is not in ui/contrast_test.go's pair table. Do not argue it up to 3:1. --rst-shadow-pop, --rst-shadow-knob, --rst-shadow-lift and --rst-overlay are not in the table: they paint a translucent layer over whatever sits underneath, not text over a fixed background, so a contrast ratio does not apply. ──────────────────────────────────────────────────────────────────── */ :root { color-scheme: light dark; /* Type: whatever the machine already has. */ --rst-font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; /* Shape: barely rounded. A corner you notice is a decision plain has not made yet. */ --rst-radius: 4px; --rst-radius-sm: 3px; --rst-radius-pill: 999px; /* Surfaces and lines */ --rst-bg: light-dark(#ffffff, #0f0f0f); --rst-surface: light-dark(#ffffff, #171717); --rst-surface-2: light-dark(#f4f4f4, #131313); --rst-line: light-dark(#e0e0e0, #2b2b2b); --rst-line-strong: light-dark(#767676, #8f8f8f); /* The page header's rule. plain has no accent hue to fold in, so it is the line and nothing else — the same derived token every theme declares, resolving here to plain grey. Decorative: it carries no contrast floor. */ --rst-header-rule: var(--rst-line); /* Text */ --rst-text: light-dark(#111111, #f2f2f2); --rst-text-muted: light-dark(#4a4a4a, #b3b3b3); --rst-text-faint: light-dark(#636363, #a0a0a0); /* Accent: none. The "accent" is the text colour, so a link is text that underlines on hover and a primary button is a filled rectangle. */ --rst-accent: light-dark(#111111, #f2f2f2); --rst-accent-strong: light-dark(#000000, #ffffff); --rst-accent-soft: light-dark(#ededed, #242424); --rst-on-accent: light-dark(#ffffff, #0f0f0f); /* Status tones: conventional hues held to very low chroma. Muted enough to read as greyscale at a glance, separated enough that an error is not a success. */ --rst-tone-neutral-fg: light-dark(#3f3f3f, #c8c8c8); --rst-tone-neutral-bg: light-dark(#eaeaea, #262626); --rst-tone-positive-fg: light-dark(#2e5b3e, #9fc8ac); --rst-tone-positive-bg: light-dark(#e4ebe6, #1a231d); --rst-tone-warning-fg: light-dark(#6a5320, #d3c08a); --rst-tone-warning-bg: light-dark(#efeadd, #262218); --rst-tone-negative-fg: light-dark(#7a2c2c, #dba3a3); --rst-tone-negative-bg: light-dark(#f0e3e3, #2a1c1c); /* Depth: one hairline layer. Real values, minimal effect. */ --rst-shadow-pop: 0 1px 3px light-dark(rgba(0, 0, 0, 0.14), rgba(0, 0, 0, 0.5)); --rst-shadow-knob: 0 1px 1px light-dark(rgba(0, 0, 0, 0.22), rgba(0, 0, 0, 0.4)); --rst-shadow-knob-hover: var(--rst-shadow-knob); --rst-shadow-lift: 0 1px 1px light-dark(rgba(0, 0, 0, 0.07), rgba(0, 0, 0, 0.3)); --rst-overlay: light-dark(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.6)); /* The depth axis, declared as the semantic tokens tokens.css read in each place before the axis existed: the fills are the accent, the faces the surface, the tints the soft accent, the linked rows the second surface, the borders, the off track and the focused edge the strong line, the rim the line, the danger fill the negative tone; the highlights, shadows, sheen and press are transparent and 0. tokens.css reads every one of these, so a theme cannot leave them out; it can only turn them off or point them back, which is what this does, so this theme renders as it did, and an app's override of --rst-accent, --rst-surface or --rst-line on :root still reaches the controls it reached before (Astra A2). */ --rst-accent-fill: var(--rst-accent); --rst-accent-fill-hover: var(--rst-accent-strong); --rst-accent-fill-edge: var(--rst-accent); --rst-accent-fill-edge-hover: var(--rst-accent-strong); --rst-on-accent-fill: var(--rst-on-accent); --rst-danger-fill: var(--rst-tone-negative-fg); --rst-danger-fill-hover: var(--rst-tone-negative-fg); --rst-danger-fill-edge: var(--rst-tone-negative-fg); --rst-danger-fill-edge-hover: var(--rst-tone-negative-fg); --rst-on-danger-fill: var(--rst-on-accent); --rst-on-danger-fill-hover: var(--rst-on-accent); --rst-danger-hover-filter: brightness(0.9); --rst-control-face: var(--rst-surface); --rst-control-face-hover: var(--rst-surface); --rst-control-face-press: transparent; --rst-control-border: var(--rst-line-strong); --rst-control-border-hover: var(--rst-accent); --rst-control-text-hover: var(--rst-accent); --rst-ghost-edge: var(--rst-line-strong); --rst-ghost-face-hover: transparent; --rst-ghost-edge-hover: var(--rst-line-strong); --rst-summary-face: transparent; --rst-summary-face-hover: transparent; --rst-summary-edge: transparent; --rst-summary-edge-hover: transparent; --rst-summary-radius: 0; --rst-depth-hl: transparent; --rst-depth-contact: transparent; --rst-depth-ambient: transparent; --rst-depth-face: 0; --rst-press-drop: 0px; --rst-knob: var(--rst-surface); --rst-knob-shade: var(--rst-surface); --rst-track-off: var(--rst-line-strong); /* The switch's one size, under a mouse and a finger alike: today's. */ --rst-switch-w: 34px; --rst-switch-h: 19px; --rst-switch-knob: 15px; --rst-switch-touch-w: 34px; --rst-switch-touch-h: 19px; --rst-switch-touch-knob: 15px; --rst-tint-hover: var(--rst-accent-soft); --rst-tint-press: var(--rst-accent-soft); --rst-tint-tap: transparent; --rst-tint-card-hover: transparent; --rst-tint-tile-hover: var(--rst-surface); --rst-tint-tile-press: var(--rst-surface); --rst-tint-group-hover: transparent; --rst-row-tint-hover: var(--rst-surface-2); --rst-row-tint-press: var(--rst-surface-2); --rst-focus-edge: var(--rst-line-strong); --rst-focus-halo: transparent; --rst-sheen-fill: transparent; --rst-sheen-plain: transparent; --rst-light-rim: var(--rst-line); --rst-light-wash: transparent; --rst-press-wash: 0; --rst-press-spot: 0; --rst-press-wash-plain: 0; --rst-press-spot-plain: 0; --rst-title-size: 1.375rem; --rst-title-weight: 600; --rst-title-track: -0.015em; --rst-heading-size: var(--rst-fs-base); --rst-label-case: uppercase; --rst-label-track: 0.06em; --rst-rail-bg: var(--rst-surface); --rst-nav-current-bg: var(--rst-accent-soft); --rst-nav-current-fg: var(--rst-accent); } /* The whole toggle. Setting color-scheme re-resolves every light-dark() above, so an explicit choice beats the OS in both directions without restating a colour. */ :root[data-theme="light"] { color-scheme: light; } :root[data-theme="dark"] { color-scheme: dark; }