Accessibility
Not a checklist bolted on at the end — these are tokens and defaults, so a component gets them by existing. Everything below is already active on this page.
Accessibility means the site works for every reader — keyboard-only, screen-reader,
low-vision, colour-blind, motion-sensitive, on a cracked phone in sunlight. It is not
a compliance checklist bolted on at launch; it is the same craft as design: contrast
is legibility, focus order is layout, labels are copywriting. Below is the system's
contract — ten points, each demonstrated live, shipped as a foundation file
(08-a11y.css) rather than an audit.
Two-tone by design: a halo in the opposite value, then the accent
ring. That's what makes one ring readable on white, on near-black
and on a photo. Mouse users never see it —
:focus-visible, not
:focus — and it is never removed
without a replacement.
:focus-visible · --focus-ring + --focus-ring-alt · .focus-halo for media surfaces
The first tab stop on every page jumps past the nav to
#main. It's off-screen until focused,
then it's a real, styled control — not a 1px sliver.
.sr-only for text only assistive tech
needs; .sr-only-focusable for things
that should appear when tabbed to. An icon-only control always
carries an aria-label and its
<svg> is
aria-hidden.
This sentence is only read aloud.
.skip-link · .sr-only · .sr-only-focusable
.tap-target expands the hit area instead of making the control bigger — the layout stays tight, the target doesn't.
--tap-min 44px · .tap-target
Every status carries a word and a glyph as well as a hue, so it survives colour blindness, a greyscale print and a bad projector.
status = colour + text + shape
Under prefers-reduced-motion every
animation collapses to a 160ms fade and all loops stop — the reveal
still happens, it just stops moving.
Under Windows high-contrast, patterns and scrims disappear and structure survives, because the structure is built from real borders rather than from background images.
@media (prefers-reduced-motion) · @media (forced-colors: active)
The invalid state is driven by aria-invalid
and the message is wired with aria-describedby,
so the styling and the screen reader can never disagree. Never “Invalid
input” — say the fix.
aria-invalid · aria-describedby · .error-text
Tabs use aria-selected, nav uses
aria-current, toggles use
aria-pressed, and the CSS styles
those attributes. There is no .is-active
anywhere in the system — one source of truth means the visual state
and the announced state cannot drift apart.
The resume and any article print as ink on paper: chrome hidden, textures off, link URLs spelled out, cards unbroken across pages.
[aria-selected] · [aria-current] · [aria-pressed] · @media print