Accessibility with Tailwind: Focus, Screen Readers and Motion

Advanced
12 min

Accessibility with Tailwind: Focus, Screen Readers and Motion

Utility classes make it easy to build something that looks finished but cannot be used with a keyboard, a screen reader or a reduced-motion setting. Tailwind ships the variants needed to fix each problem; the work is knowing which to reach for. In this lesson you will learn the focus-ring pattern, text for assistive technology, contrast and motion preferences, and touch target sizing.

Visible focus without annoying mouse users

Every interactive element must show where keyboard focus is. The browser outline does this but also appears on mouse clicks, so teams remove it — and forget to replace it. focus-visible: applies only when the browser decides focus should be shown:

html
<button class="rounded-md bg-blue-600 px-4 py-2 text-white focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"> Continue </button> <input class="rounded-md border px-3 py-2 focus:border-blue-500 focus:outline-hidden focus:ring-2 focus:ring-blue-500/30" />

Use focus-visible: for buttons and links and plain focus: for text inputs, where a ring on click helps. Prefer outline-hidden over outline-none: both hide the outline, but outline-hidden keeps a transparent one that reappears in Windows High Contrast mode. Never remove focus styles without adding a ring.

A skip link lets keyboard users jump past the navigation: hidden with sr-only, revealed on focus with focus:not-sr-only, as in the sample code.

Text for screen readers

Icons, color and position carry meaning assistive technology cannot see. Provide it as text:

html
<!-- Icon-only button --> <button class="p-2"> <svg class="size-5" aria-hidden="true">...</svg> <span class="sr-only">Close dialog</span> </button> <!-- Status conveyed by color and by text --> <span class="inline-flex items-center gap-1 text-sm text-green-700"> <span class="size-2 rounded-full bg-green-500" aria-hidden="true"></span> Online </span>

sr-only positions text off-screen while keeping it in the accessibility tree; aria-hidden="true" removes decorative graphics from it. Keep visual and announced state in sync by styling from the attribute — aria-expanded:rotate-180, aria-selected:bg-blue-50 — instead of toggling separate classes.

Contrast, forced colors and user preferences

Text needs a contrast ratio of at least 4.5:1 against its background (3:1 for large text). In the default palette, gray-500 on white is borderline; gray-600 passes, and every dark: pair needs checking again. Two variants adapt to users who have asked for more: contrast-more: when the OS requests higher contrast, and forced-colors: in Windows High Contrast mode.

html
<p class="text-gray-600 contrast-more:text-gray-950">Helper text</p> <div class="bg-blue-600 text-white forced-colors:border forced-colors:border-current">Card</div>

In forced-colors mode the browser replaces your colors with the system palette, so anything defined only by background disappears; a border under forced-colors: keeps the shape visible.

Motion, touch targets and anchors

Three more preferences are one variant each:

html
<div class="motion-safe:animate-bounce motion-reduce:animate-none">Scroll hint</div> <button class="min-h-11 min-w-11 p-2 pointer-coarse:p-3">Tap me</button> <h2 id="pricing" class="scroll-mt-24">Pricing</h2>
  • motion-safe: and motion-reduce: respect the reduced-motion setting; put animations behind motion-safe: by default.
  • Touch targets should be at least 44 by 44 CSS pixels: min-h-11 min-w-11. pointer-coarse: (v4.1+) adds padding only for touch input.
  • scroll-mt-* offsets anchor targets so a sticky header does not cover them, and motion-safe:scroll-smooth on <html> animates the jump only when allowed.

Common mistakes

  • Using outline-none on everything, which removes focus for keyboard users and for High Contrast mode.
  • Relying on hover: alone to reveal controls; pair it with focus-within: or focus-visible:.
  • Using text-gray-400 for body text; it fails contrast on white.
Quick Quiz
Question 1 of 2

Why is `outline-hidden` preferred over `outline-none` when adding a custom focus ring?

Key Takeaways

  • Replace outlines with focus-visible:ring-2 plus outline-hidden, never with outline-none alone.
  • Give icon-only controls sr-only text and mark decorative graphics aria-hidden; style state from aria-* attributes.
  • Meet 4.5:1 contrast, and use contrast-more: and forced-colors: to adapt to user settings.
  • Gate animation behind motion-safe:, size targets with min-h-11 min-w-11, and offset anchors with scroll-mt-*.
  • A skip link is sr-only focus:not-sr-only with positioning applied on focus.

Next lesson: Tailwind with React and Vue: clsx, tailwind-merge and cva — manage conditional classes and component variants in JavaScript frameworks.

Accessibility with Tailwind: Focus, Screen Readers and Motion - Tailwind CSS | CodeYourCraft | CodeYourCraft