Accessible CSS

CSS can make a page accessible or break it. Colour contrast, focus visibility, readable text, motion, hidden content and layout order all depend on your styles. This lesson gathers the CSS habits that keep pages usable for everyone.

Contrast

Try it yourself
Result

WCAG requires a contrast ratio of at least 4.5:1 for normal text, 3:1 for large text (about 24px, or 19px bold) and for interface parts like input borders and icons. Browser developer tools show the ratio when you inspect a colour.

Visible focus

Try it yourself
Result

Keyboard users need to see where they are at all times. A global :focus-visible style with good contrast is one of the most valuable lines of CSS you can write. Never remove outlines without an equally visible replacement.

Respect text resizing and zoom

  • Use rem for font sizes so users' browser font-size settings apply.
  • Avoid fixed heights on elements containing text — use min-height so text can grow.
  • Never disable zooming in the viewport meta tag.
  • Test at 200% zoom: content should reflow into one column without horizontal scrolling or overlapping.
Try it yourself
Result

Reduced motion

CSS motion.css
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Large movements, parallax and zooming can trigger dizziness and nausea for people with vestibular disorders. Honour the system setting — either globally like this, or by designing a calmer alternative for each animation.

Hide content correctly

GoalUse
Hide from everyonedisplay: none or the HTML hidden attribute
Hide visually, keep for screen readersA .visually-hidden utility class
Hide decorations from screen readersaria-hidden="true" in HTML, or content: "★" / "" for generated text
CSS visually-hidden.css
.visually-hidden:not(:focus):not(:active) {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

The :not(:focus) part lets the same class be used for "skip to content" links that appear when focused.

Keep visual order and DOM order in sync

Flexbox order, row-reverse, grid placement and absolute positioning can make the visual order differ from the HTML order. Screen readers and Tab follow the HTML. If something is first visually, it should usually be first in the HTML too.

Don't rely on colour alone

Try it yourself
Result

Touch targets and spacing

CSS targets.css
.icon-button {
  min-width: 44px;
  min-height: 44px;
}

.inline-links a {
  padding-block: 0.25em;
}

Make interactive elements at least 24×24 CSS pixels (44×44 is better), with space between them, so people with limited dexterity — and everyone on a phone — can tap accurately.

Forced colours and high contrast

Windows High Contrast mode replaces your colours with the user's chosen palette, removing backgrounds and shadows. Use real borders or outlines for important boundaries (a transparent border is enough — it becomes visible in forced-colours mode), and test with @media (forced-colors: active).

Checklist

  • Text and UI contrast meet 4.5:1 / 3:1.
  • Every interactive element has a visible focus style.
  • Font sizes in rem; no fixed heights on text containers.
  • Page works at 200% zoom and 320px wide.
  • Motion respects prefers-reduced-motion.
  • Visual order matches DOM order.
  • Information never conveyed by colour alone.

Try it yourself

Fix the problems: the low-contrast text, the missing focus style and the colour-only error.

Try it yourself
Result