Accessibility
Accessibility is a default, not a feature — the system ships the primitives; this page shows where they live.
Focus rings
The focus-ring mixin in scss/_mixins.scss is the single source of truth for visible keyboard focus. It targets :focus-visible (so mouse clicks never light up a button mid-press) and draws a 3-pixel ring in var(--border-focus), which every theme overrides to a colour that passes WCAG contrast against its own background. Pass a non-zero $offset and the mixin switches from a box-shadow ring to a real outline with outline-offset — useful when an element has its own shadow you do not want the ring to fight with.
// signature @mixin focus-ring($color: border-focus, $width: 3px, $offset: 0); // usage on a custom element .my-link { @include m.focus-ring; } // with outline-offset for elements that already cast a shadow .chip { @include m.focus-ring($offset: 2px); }
Every interactive cia component — Button, Input, Select, Tabs, Dropdown, the dismiss control inside Alert — already includes focus-ring through its base mixin. You only need to call it yourself on bespoke interactive elements you compose outside the component set.
Colour contrast
Status tokens — --success-default, --warning-default, --error-default, --info-default — are defined in every one of the six shipped themes and are tuned so that text set in the token reaches WCAG AA (4.5:1 for body copy, 3:1 for large text) against its intended surface. The same holds for --text-default against --surface-default and --border-focus against whatever component it wraps. See /docs/tokens#palette for the full token gallery per theme.
Payment received
Could not save
<Alert status="success" title="Payment received"> We processed your order and emailed a receipt. </Alert> <Alert status="error" title="Could not save"> The server rejected three of the nine fields. </Alert>
Screen-reader-only text
The .cia-sr-only utility hides content visually while keeping it in the accessibility tree, so assistive technology still announces it. Use it to label icon-only controls, annotate decorative layout, or expose headings that a sighted user gets from visual structure. A matching .cia-not-sr-only utility reverses the effect — useful for skip links that should appear on focus.
/* from scss/_utilities.scss */ .cia-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; }
<button class="cia-btn-ghost"> <span aria-hidden="true">×</span> <span class="cia-sr-only">Close dialog</span> </button>
A parallel @mixin sr-only lives in scss/_mixins.scss when you would rather compose the same declarations into your own class.
Keyboard navigation
Every interactive cia React component — Tabs, Dropdown, Modal, Tooltip, DataTable, Accordion — ships with keyboard handling wired up: arrow keys for roving tab-index inside menus and tablists, Escape to close overlays, Enter and Space to activate. For custom interactive elements you build yourself, reach for native <button>, <a href> or <input> before writing a <div onClick> — the browser gives you focus management, keyboard activation and correct role for free.
When you genuinely need a custom widget pattern (a split button, a command palette, a combobox), the WAI-ARIA Authoring Practices documents every pattern with the keyboard interaction model you are expected to match.
ARIA patterns
Each high-level cia component implements a named pattern from the Authoring Practices Guide. The table below maps them so you know exactly what behaviour is already covered.
| Component | Pattern |
|---|---|
<Modal> | Dialog, with focus trap and restore on close |
<Tabs> | Tabs, with arrow-key navigation between tabs |
<Dropdown> | Menu, with Escape to close and arrow keys to move |
<Tooltip> | Tooltip, linked to its target with aria-describedby |
<Accordion> | Disclosure, one button per collapsible region |
Prefers-reduced-motion
Every animation shipped in the system is wrapped in a @media (prefers-reduced-motion: reduce) guard that shortens durations to near-zero and skips non-essential transforms. See /docs/animation#reduced-motion for the implementation and the mixins that honour the preference automatically.
Skip to content
Recommendation. The current SiteHeader does not yet include a skip link. A standard implementation pairs .cia-sr-only with .cia-not-sr-only on focus, so the link is invisible until a keyboard user tabs onto the first focusable element of the page. Target the page's main landmark by id.
<!-- first element inside <body>, before the header --> <a class="cia-sr-only focus:cia-not-sr-only" href="#main"> Skip to content </a> <!-- ... header, nav ... --> <main id="main"> <!-- page content --> </main>
If you are writing the CSS yourself rather than composing utilities, the minimal rule is: absolutely position the link off-screen, then reveal it with :focus or :focus-visible.
Writing accessible components
- Use semantic HTML before ARIA — a
<button>is always better than a<div role="button">, and<nav>beats<div role="navigation">. - Mixins provide focus rings — do not remove
outlineorbox-shadowon:focus-visiblewithout a replacement. If the default ring fights your design, pass a different$coloror$offsettofocus-ring. - Every form input needs a
<label>. Either wrap the input in one, associate them withfor/id, or use<FormField>from cia which wires the relationship for you. - Colour is never the only cue. Pair status colour with an icon, a text label, or both — users with low vision, colour-blindness or a monochrome display still need to tell error from success.
- Test keyboard-only navigation on every interactive component you ship. Unplug the mouse, tab through the page, and verify that every action has a visible focus state and a keyboard activation path.