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 readBy Leonie RoesmannUpdated: 12 July 2026
AccessibilityReactTailwind

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"   Book an appointment</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={active}  className={active ? "bg-neutral-900 text-white" : "bg-neutral-100"}   Weekly</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
Leonie 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.

Yes, restyling is explicitly fine. What is not fine is removing it with nothing in its place. A custom ring in your brand colour with enough contrast to the background is often better than the browser default, because it fits your interface and therefore does not look like a bug.

Browser dev tools show the ratio right in the colour picker as soon as you click a text colour. For a quick check: light greys on white almost always fail. Anything below roughly 60 percent black on white is worth measuring.

Yes, and an empty one, not a missing one. If the attribute is absent entirely, a screen reader falls back to reading out the file name. An empty alt explicitly says: nothing to hear here, skip it.

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

Get in touch