Accessible Styling
Focus indicators
Never remove the focus outline — naked
Keyboard users track their position by the focus indicator. Deleting it with
outline: none is the most common CSS accessibility failure. Use
:focus-visible to show a strong indicator for keyboard focus while leaving mouse clicks
clean, and always replace, never just remove:
/* Wrong: keyboard users are now lost
.button:focus { outline: none; } */
/* Right: a visible, high-contrast ring for keyboard focus */
.button:focus-visible {
outline: 3px solid #93c5fd;
outline-offset: 2px; /* the ring floats clear of the button's edge */
}
/* WCAG 2.4.7 (focus visible) + 2.4.13 (focus appearance, WCAG 2.2) */
Forced colors mode
/* Windows High Contrast Mode strips your colors: test with it on.
Respect the user's palette where it matters, restore structure
where meaning would otherwise be lost. */
@media (forced-colors: active) {
.button { border: 1px solid ButtonText; } /* keep the boundary visible */
}
Contrast and size
The numbers to hit
| Content | WCAG AA minimum | Notes |
|---|---|---|
| Body text | 4.5 : 1 | Measured against the actual background — gradients included |
| Large text (≥24px, or ≥18.66px bold) | 3 : 1 | The "large text" discount is narrower than most teams assume |
| UI boundaries (inputs, icons, focus) | 3 : 1 | WCAG 1.4.11 non-text contrast |
| Placeholder text | 4.5 : 1 | Placeholders vanish once typing starts anyway — label properly |
/* A contrast-safe dark theme pair: measure it, do not eyeball it */
body { color: #e5e7eb; background: #0f172a; } /* ~13:1 */
.muted { color: #94a3b8; } /* ~7:1 - passes for body text */
.faint { color: #64748b; } /* ~4.6:1 - body text only, never smaller */
Text that survives zoom
WCAG 1.4.4 requires the page to work at 200% zoom; 1.4.10 requires reflow at 320px width with 400% zoom.
In practice: no fixed heights on text containers, no horizontal-scroll regions for prose, and font sizes
in rem so zoom compounds sensibly.
Interactive targets
The 44px rule and its cousins
WCAG 2.5.8 (target size, minimum) requires a 24 × 24 CSS-pixel target; WCAG 2.5.5 (enhanced) requires 44 × 44. Touch is only part of the story — tremor and low-precision pointers benefit too. If the visual control must be small, expand its hit area transparently:
/* A small visual icon button with a full-size hit area */
.icon-btn {
position: relative;
width: 24px;
height: 24px;
}
.icon-btn::after {
content: "";
position: absolute;
inset: -10px; /* the ::after IS the click target: 44px total */
}
Styling that hides meaning
Properties with side effects on assistive technology
/* display: none and visibility: hidden remove content from the
accessibility tree as well as the screen - fine, when intentional. */
/* The screen-reader-only utility: visually gone, still announced */
.visually-hidden {
position: absolute;
width: 1px; height: 1px;
clip-path: inset(50%);
overflow: hidden;
white-space: nowrap;
}
/* Danger: content rendered ONLY in ::before/::after is invisible
to some assistive tech - never put meaningful text there. */
/* Danger: color alone never carries meaning (WCAG 1.4.1). Pair it: */
.error { color: #ef4444; } /* not enough alone */
.error::before { content: "⚠ "; } /* a non-color cue as well */
The prefers-* family
Media queries for human preferences
/* Reduced motion: kill travel, keep feedback (Animations lesson) */
@media (prefers-reduced-motion: reduce) { /* ... */ }
/* Dark and light themes: define tokens for each (Variables lesson) */
@media (prefers-color-scheme: dark) { :root { --bg: #0f172a; } }
/* The user asked for more contrast than your design ships */
@media (prefers-contrast: more) {
.muted { color: #e5e7eb; } /* stop muting text */
}
/* Reduced transparency: replace glassmorphism with solid fills */
@media (prefers-reduced-transparency: reduce) {
.toolbar { background: #1e293b; backdrop-filter: none; }
}
Practice: an accessibility pass
The task
Open demo/quality/19_accessible_styling.html from the
Lab Examples page, then audit one of your own pages with
the checklist below and the WebAIM contrast checker (linked from
References).
The checklist
- Tab through the page with the mouse untouched: is every stop visible?
- Run the text/background pairs through a contrast checker; fix every body-text failure.
- Confirm no interactive target is smaller than 24 × 24 CSS pixels.
- Zoom to 200% and then to 400%: no information is cut off or requires horizontal scrolling.
- Verify no rule communicates by color alone.