Skip to content
Accessibility

Accessible components with React and Tailwind

Build accessible components: semantic HTML, focus states, targeted ARIA, contrast and keyboard operation with React and Tailwind CSS.

10 min readUpdated: 12 July 2026
AccessibilityReactTailwind
The right element01
Series 04
Light that stays all day
Six lamps from one workshop in Utrecht.
See the rangeShowroom

Accessibility is not a topping you sprinkle on at the end. It grows out of a component's structure. Those who use semantic HTML, make focus visible and take the keyboard seriously build interfaces that work for more people. This guide shows the most important building blocks with React and Tailwind CSS.

The short version
  • Accessibility starts with the right element. A button is a button, not a div with onClick.
  • Everything that works with a mouse has to work with a keyboard. That is the fastest test.
  • The focus ring is not a blemish. Removing it takes away people's orientation.
  • Contrast is measurable: 4.5 to 1 for body text, 3 to 1 for large type and controls.
01

Start with semantic HTML

The right element is half the battle. A button is a button, not a div with onClick. Native elements come with focus behavior, keyboard operation and the right role for screen readers built in. That saves you a lot of manual follow-up work.

Structure matters too. Headings in a logical order, nav for navigation, main for the main content and lists for enumerations give assistive technologies a map of your page. In React that costs nothing extra, you simply choose the fitting tag.

A div that looks like a button
  • No focus, the keyboard never reaches it.
  • Enter and space do nothing.
  • The screen reader announces: group.
  • You rebuild role, state and keyboard by hand.
A button
  • Focus, tab order and ring are there.
  • Enter and space trigger it.
  • The screen reader announces: button.
  • All you write is what should happen.
02

Make focus visible

Those navigating by keyboard must see where they currently are. So never remove the focus ring without a replacement. In Tailwind you control it deliberately through focus-visible, so it appears on keyboard use but does not disturb on mouse clicks.

A class like focus-visible with ring-2 and a clear ring color is often enough. Make sure there is enough contrast to the background so the state stays clear even on light surfaces. A clearly visible focus is a sign of care, not a flaw.

NameEmailSubmit
Tabmoves the ring, focus-visible makes it visible.
This is the page when someone puts the mouse away. Without a visible ring there is nothing to see here.
<button  className="rounded-lg bg-neutral-900 px-5 py-3 text-white     focus-visible:outline-none focus-visible:ring-2     focus-visible:ring-neutral-900 focus-visible:ring-offset-2"   Termin buchen</button>
Ring on keyboard, quiet on click.
03

Use ARIA only when needed

ARIA attributes add meaning that does not come from the HTML alone. But the first rule is: no ARIA is better than wrong ARIA. Where a native element suffices, you need no extra role.

ARIA becomes useful for custom widgets. A toggle gets aria-pressed, an expandable menu aria-expanded, a purely decorative icon aria-hidden. For buttons without visible text, aria-label provides an understandable label. Set these attributes deliberately and keep them in sync with your component's actual state.

<button   aria-pressed={aktiv}  className={aktiv ? "bg-neutral-900 text-white" : "bg-neutral-100"}   Wöchentlich</button>
Announce the state, do not just colour it.
No ARIA beats wrong ARIAAn aria-pressed that does not track the state tells users the opposite of what they see. A wrong role overrides the correct one the element already had. Only set attributes you also maintain.
04

Consider keyboard operation

Everything that works with the mouse should also work with the keyboard. Native buttons and links already respond to Enter and Space. If you build interactive elements yourself, you have to provide these keys and a sensible tab order on your own.

For overlays like dialogs, focus comes into play. On opening it moves into the dialog, on closing back to the triggering element, and while it is open it stays trapped inside the dialog. This focus management decides whether a component is truly operable.

Focus in a dialogThree rules and an overlay is usable: on open the focus moves in, while open it stays inside, on close it returns to the element that opened it. The native dialog element brings all three.
05

Check contrast and colors

Text must stand out clearly from the background. As a guideline, at least 4.5 to 1 for normal text and 3 to 1 for large type. Very light grays on white look elegant but often fail this test.

Color should also never be the only carrier of information. An error state needs a word or an icon next to the red, an active link more than just a different color. That keeps your interface understandable for people with limited color vision too.

Body text on a surface2.4:1 fail
Body text on a surface4.6:1 pass
Body text on a surface14.7:1 pass
Body text on a surface3.1:1 fail
Target: 4.5:1 for body text, 3:1 for large type and controls.
Two of these four combinations fail. Both look good in the mockup, and that is exactly the trap.
06

Account for images and motion

Every content-carrying image needs a short, fitting alternative in the alt attribute. Purely decorative images get an empty alt so screen readers skip them and do not interrupt the reading flow with decoration.

Motion should be possible to turn off. Through prefers-reduced-motion and the hook useReducedMotion you offer a calm variant. Together with good contrast and clear structure your component becomes usable for many more people.

A good alt text is shortDescribe what someone needs to know, not what is visible. For a portrait under a quote the name is enough. Words like image or photo can go, the screen reader says that anyway.
07

The five minute test

You do not need an audit tool to find the obvious problems. These four moves surface most of them.

  1. Put the mouse away. Move through the page with Tab. Can you reach everything, and can you always see where you are?
  2. Zoom to 200 percent. Does the layout break, does text overlap, does something disappear? Zoom is not an edge case, many people browse that way all the time.
  3. Check the colors. Grey text on a light background is the classic. 4.5 to 1 for body text, 3 to 1 for large type.
  4. Turn off images. What remains is what a screen reader hears. Decoration needs an empty alt, content needs a short description.

Accessible does not mean plainer. It means the design still holds when someone operates it differently.

08

Where Tailwind helps and where it does not

Tailwind does not make a page accessible, but it makes the right things convenient. focus-visible is its own variant, sr-only ships as a utility, and tokens keep contrast in one place.

What Tailwind does not do for you: pick the right element, write labels, announce state. A div with a hover class looks like a button and still is not one.

Share
Written by
Amelie RoesmannCreative directionLeonie RoesmannDesign engineering

We build websites and web apps at Systra Studios in Münster and put the building blocks from that work here. What you read comes from real projects, not from a brochure.

Systra Studios: web design from Münster

You know the rules.
The detail work is already done.

Forms and controls with labels, visible focus and checked contrast. Copy, swap the copy, keep building.

  • Every field has a label, every button a name.
  • focus-visible is set, not removed.
  • Motion, reduced motion and dark mode are built in.
Browse form sections
FAQ

Frequently asked questions

Tools find roughly a third of the problems, mostly missing labels and weak contrast. Whether the tab order makes sense, whether a dialog traps focus or whether an error is announced, you have to walk through yourself. Five minutes on the keyboard beats any report.

Didn't find your question? We're happy to help.

Get in touch