Subconscious design system
Accessibility contrast matrix · focus rings · motion · semantics

Read it or write it again.

Accessibility is a property of the system. Not a checklist.

§A · Contrast matrix · every sanctioned pair · WCAG 2.1
Pair
Text
Background
Ratio
Grade
Use
ink / ivory (default body)
The shape
var(--backgroundColor)
17.41:1
AAA
body
ink / ivory-2 (in a card)
The shape
color-mix(in srgb, var(--color) 5%, var(--backgroundColor))
16.32:1
AAA
body in cards
grey-500 / ivory (asides, captions)
An aside
var(--grey-500)
6.10:1
AA
captions, meta
grey-500 / ivory-2
An aside
color-mix(in srgb, var(--color) 5%, var(--backgroundColor))
5.72:1
AA
captions in cards
red / ivory (signal channel)
● live
var(--red)
5.21:1
AA
labels, signal
red / ivory-2
● live
color-mix(in srgb, var(--color) 5%, var(--backgroundColor))
4.88:1
AA
labels in cards
ivory / ink (reverse · button)
Run simulation
var(--color)
17.41:1
AAA
primary cta
grey-300 / ink (dark theme aside)
An aside
var(--grey-300)
6.99:1
AA
caption on ink
red / ink (red on dark)
Red mark
var(--color)
2.71:1
FAIL
use a contrasting signal label
grey-300 / ivory (banned for text)
Low contrast
var(--grey-300)
2.49:1
FAIL
hairlines only
grey-500 / ink (banned for text)
Low contrast
var(--color)
2.85:1
FAIL
no text
Ratios and grades are measured live from the current theme and mode. Body text passes AA on both ivory surfaces. Red on ink fails text contrast, so red stays a non-text signal.
§B · Focus · 2px ink, 2px offset · :focus-visible only
§B.1 · resting

No outline at rest.

Mouse users never see the ring. It would be visual noise for an interaction the cursor already explains.

§B.2 · keyboard-focused

2px ink ring, 2px offset.

Keyboard and screen-reader users get an unmistakable signal: a 2px ink outline. Red is kept for the destructive ring.

Focus uses :focus-visible only. Inputs thicken to a 2px ink rule. Surfaces get a 2px outline.
§C · Skip link · first stop in the tab order

The first focusable thing on every page. Hidden until :focus-visible; then it lands at top-left, in mono caps, on ink:

Skip to main content
§D · States · what an interactive element looks like in each register
Element
Rest
Hover
Focus (kbd)
Disabled
PRIMARY
GHOST
INPUT
LINK
methodology
methodology
methodology
methodology
Disabled state lowers opacity to 0.4 and switches stroke to grey-300 — never grey-500 (which would still pass AA and make the control look dimmed but real). Disabled means dead.
§E · Motion · the prefers-reduced-motion contract
SPINNER 900ms continuous rotate (default) slows to 4s rotation when prefers-reduced-motion  
LIVE DOT 1.4s pulse · opacity 1 → 0.4 → 1 animation: none · stays at full opacity
SKELETON shimmer · 1.4s linear, infinite static --ivory-2 block (no shimmer)
DECK SLIDES 280ms ease-out cross-fade instant cut · no fade
CHART REVEALS staggered 60ms per series all series appear together · static
SCROLL native browser smooth-scroll if user has it never scroll-behavior: smooth in our CSS
Every animation in the system has a @media (prefers-reduced-motion: reduce) branch. We never remove the affordance — a spinner still spins, just slower. The brand stays recognizable; the strobe goes away.
§F · Semantics · what the markup tells the screen reader

§ Required

Headings nest. One h1 per page. Sections use h2 through h4 in order.

Landmarks. main, nav, aside, footer on every page. Skip link targets main.

Labels are real. <label for> on every input. Placeholder is never the label.

Modals trap focus. Tab cycles inside; Esc closes; focus returns to the trigger.

Live regions. Toasts post into role="status" for info/success and role="alert" for warn/error.

Icons are aria-hidden="true" when redundant with text. Standalone (close, search) carry role="img" + <title>.

§ Banned

A div with a click handler — use a button

Color as the only signal — every red item also carries a label, dot, or icon

outline: none without a replacement

Scroll-jacking, parallax, autoplay video

Tooltips for primary content — they're keyboard-hostile

Placeholder text doing the job of a label

Tabindex values higher than 0

Modal opens and focus stays on the page underneath

Body AAA  ·  Captions AA  ·  Focus 2px ink, :focus-visible  ·  Motion honors reduced-motion  ·  Doc A11Y.md