/* 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 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 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-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 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; }