Overview
Accessibility is built into the token layer, not added per component. Override tokens or Sass variables when branding; re-check focus, contrast, and motion after theme changes.
| Concern | Token / API | WCAG |
|---|---|---|
| Keyboard focus | --focus-ring, .focus-visible | 2.4.7 Focus visible |
| Touch size | --min-touch-target (44px) | 2.5.5 Target size (AAA target, AA practice) |
| Text contrast | Semantic fg/bg pairs | 1.4.3 Contrast (minimum) |
| Motion | prefers-reduced-motion | 2.3.3 Animation from interactions |
| Screen reader text | .sr-only | 1.3.1 Info and relationships |
| Hidden content | [hidden] + display: none | 4.1.2 Name, role, value |
Prerequisites
- PimentCSS installed, Installation (a11y tokens ship in the default bundle).
- Semantic colors, Colors for surfaces and text tokens used in contrast pairs.
- Both themes, toggle light/dark in the doc header while reviewing focus and swatches below.
Canonical tokens
Edit tokens/a11y.css for focus, motion, and documented contrast aliases. Sass source: scss/tokens/_a11y.scss.
:root {
--focus-ring-width: 3px;
--focus-ring-offset: 2px;
--focus-ring-color: var(--primary-400);
--focus-ring: var(--focus-ring-width) solid var(--focus-ring-color);
--min-touch-target: 44px;
}
.focus-visible:focus-visible {
outline: var(--focus-ring);
outline-offset: var(--focus-ring-offset);
}
Visible focus
Interactive elements use :focus-visible (not :focus on every click) with a 3px ring. Apply .focus-visible on custom controls or use the focus-ring mixin in Sass. In documentation previews, .focus-visible simulates the ring without tabbing.
<button type="button" class="btn btn--primary">Save</button>
Tab through the demo above to see live :focus-visible on each control.
Touch targets
Buttons, checkboxes, radios, switches, and theme toggles respect min-height: var(--min-touch-target) (44px). Icon-only buttons use the same minimum square size via .btn--icon-only.
@include touch-target; /* min 44×44px */
.btn--icon-only {
min-width: var(--min-touch-target);
min-height: var(--min-touch-target);
}
Contrast pairs
Use semantic foreground/background tokens in UI. Target ≥ 4.5:1 for normal text (AA). Large text (≥ 18px regular or 14px bold) may use 3:1. Non-text UI boundaries and focus indicators need ≥ 3:1 against adjacent colors (WCAG 1.4.11).
--surface-page
Body copy
--surface-action
Primary button
--error-100
Inline error
--surface-elevated
Card surface
| Usage | Text | Background | Target |
|---|---|---|---|
| Page body | --text-body | --surface-page | AA 4.5:1 |
| Primary button | --text-on-action | --surface-action | AA 4.5:1 |
| Error message | --error-700 | --error-100 | AA 4.5:1 |
| Success on tint | --success-900 | --success-100 | AA 4.5:1 |
Full palette and live ratios: Colors → Semantic contrast pairs.
Screen reader only
Supplement visible labels with .sr-only when context is missing visually (icon-only buttons, redundant link text). Documentation chrome uses .pdoc-sr-only with the same clipping pattern.
<button type="button" class="btn btn--primary btn--icon-only" aria-label="Close dialog"><span class="btn__icon" aria-hidden="true">×</span></button>
Hidden content
Always use the native hidden attribute (or aria-hidden="true" for decorative icons). Global CSS sets [hidden] { display: none !important; } so collapsed tree groups and loaders do not remain focusable.
<ul class="tree__group" hidden="">…</ul>
Reduced motion
When users enable Reduce motion at the OS level, animations and transitions are minimized site-wide:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Avoid animating large properties (box-shadow, width) for state changes; prefer opacity, border, or instant swaps. Test carousels, loaders, and snackbars with reduced motion enabled.
Utilities & classes
| Class | Role |
|---|---|
.focus-visible | Applies --focus-ring on :focus-visible |
.sr-only | Visually hidden, available to assistive tech |
Customize (Sass)
-
Override focus and touch tokens
Pass variables when importing PimentCSS. Keep ring width ≥ 2px and sufficient contrast against adjacent surfaces.@use "pimentcss-design-system" with ( $focus-ring-width: 3px, $focus-ring-color: var(--accent-500), $min-touch-target: 44px, ); -
Rebuild CSS
Regeneratedist/pimentcss.min.cssafter token edits.npm run build:css
See Customization for partial imports. Components use @include focus-ring and @include touch-target from scss/abstracts/_mixins.scss.
Review checklist
Before shipping UI
- Keyboard, tab order logical; focus visible on every interactive control; no focus trap without escape.
- Names, buttons and links have accessible names; form fields have associated
<label>oraria-label. - Color, do not rely on hue alone; pair with text, icons, or weight (RGAA thématique 3).
- Contrast, verify light and dark; use semantic tokens from Colors.
- Motion, test with
prefers-reduced-motion: reduce. - Dialogs,
role="dialog",aria-modal="true", focus management, see Modals.