Back to Blog
Frontend12 min readJun 2026

Web Accessibility (a11y): Building UIs Everyone Can Use

Most accessibility is free if you start with semantic HTML. Here is how the four POUR principles, keyboard navigation, contrast, and accessible names fit together, and why your first rule of ARIA should be to not use ARIA.

FrontendAccessibilitya11yARIA
SB

Sri Balaji

Founder · TheSimplifiedTech

On this page

The button that isn't a button

Here is a "button" that ships to production every day. It looks perfect. It has hover states, a nice gradient, and the click handler works:

DivButton.tsx
tsx
<div className="btn" onClick={handleCheckout}>
  Checkout
</div>

Now try to use it without a mouse. Press Tab, your focus skips right past it, because a div is not focusable. Press Enter or Space, nothing happens, because a div has no built-in click behavior for the keyboard. Turn on a screen reader and it announces… nothing useful. It is just "Checkout, group." A blind user, a keyboard-only user, someone with a tremor using switch control, none of them can check out. You did not mean to lock them out. The markup did it for you.

Who this is for

Frontend developers who can build a working UI but have never thought hard about who *can't* use it. No prior accessibility knowledge assumed. By the end you will know what to do, what not to do, and how to check your work.

Accessibility is just usability, for everyone

Accessibility is not a feature you bolt on for a minority. It is usability, for everyone, and most of it is free if you start with semantic HTML.

The web was *born* accessible. A plain HTML document with headings, links, and form controls works with a screen reader, a keyboard, voice control, and a braille display out of the box. We break that accessibility ourselves, by replacing <button> with <div>, by removing focus outlines because they "look ugly," by choosing grey-on-grey text that looks elegant in Figma. Accessibility work is mostly *not breaking* what the browser already gives you.

A curb cut (the ramp in a sidewalk) was built for wheelchairsSemantic HTML was built for screen readers
But it also helps strollers, luggage, delivery carts, and cyclistsBut it also helps keyboard users, voice control, SEO, and automated tests
Nobody complains the sidewalk has a rampNobody complains your button is a real `<button>`
Accessibility helps the people you designed for too.

This is the curb-cut effect: design for the edge and you improve the center. Captions help deaf users *and* anyone watching on a muted phone in a quiet office. Good contrast helps low-vision users *and* anyone outside in sunlight. Keyboard support helps motor-impaired users *and* power users who never touch the mouse. Accessibility is not charity, it is good engineering with a wider blast radius.

How assistive tech actually sees your page

To reason about accessibility you need one mental model: the accessibility tree. When the browser parses your HTML it builds the DOM, and alongside it, a second, simplified tree describing what each element *is* and *does*: its role (button, link, heading), its name ("Checkout"), and its state (pressed, disabled, expanded). Screen readers and other assistive tech read *that* tree, not your CSS.

rendersexposesannouncesdrives focus
Semantic HTML

<button>, <nav>, <h1>

DOM

Visual render

Accessibility Tree

role · name · state

Screen Reader

VoiceOver / NVDA

Keyboard

Tab / Enter / Space

Semantic HTML feeds the accessibility tree, which is what assistive tech reads.

A <div onClick> shows up in the DOM, so it renders and clicks. But in the accessibility tree it has no role, no name, and no keyboard behavior, so the screen reader and keyboard paths simply have nothing to grab onto. A real <button> populates all three. That single difference is most of accessibility.

  1. 1

    You write semantic HTML

    Use the element that means what you intend: <button> for actions, <a href> for navigation, <h1>–<h6> for structure, <label> for inputs.

  2. 2

    The browser builds the accessibility tree

    Each element gets a role, an accessible name, and state, derived automatically from the tag and its content.

  3. 3

    Assistive tech reads the tree

    A screen reader announces "Checkout, button." The Tab key stops on it. Enter and Space activate it. You wrote none of that logic.

  4. 4

    The user acts

    They hear what the control is, reach it with the keyboard, and operate it, exactly like a sighted mouse user, through a different door.

The four POUR principles

The Web Content Accessibility Guidelines (WCAG) organize everything under four principles, abbreviated POUR. You do not need to memorize the spec, you need the gist of what each one asks of your UI.

PrincipleThe question it asksIn practice
PerceivableCan users sense the content?Alt text on images, captions on video, enough color contrast, never rely on color alone to convey meaning
OperableCan users drive the UI?Everything works by keyboard, focus is visible, no keyboard traps, enough time to act, no seizure-inducing flashes
UnderstandableCan users make sense of it?Clear labels, predictable behavior, helpful error messages, a declared page language
RobustWill it work with their tools?Valid HTML and correct roles/names/states so any browser or assistive tech can interpret it
POUR, what each principle means at the keyboard.

A pocket test

Unplug your mouse and use your app with the keyboard alone. Then zoom the browser to 200%. Those two checks surface the majority of Operable and Perceivable failures in minutes.

Div-button vs. real button

Let us fix the opening example. The naive instinct is to patch the div until it behaves like a button. Watch how much code that takes, and how much the right element gives you for free.

Inaccessible.tsx
tsx
// Inaccessible: a div pretending to be a button
<div className="btn" onClick={handleCheckout}>
  Checkout
</div>

// "Fixing" the div means re-implementing the browser:
<div
  className="btn"
  role="button"            // 1. fake a role
  tabIndex={0}             // 2. make it focusable
  onClick={handleCheckout}
  onKeyDown={(e) => {       // 3. wire up keyboard yourself
    if (e.key === "Enter" || e.key === " ") handleCheckout();
  }}
>
  Checkout
</div>
Accessible.tsx
tsx
// Accessible: just use the element that already does all of that
<button className="btn" onClick={handleCheckout}>
  Checkout
</button>

// Focus is visible by default, keep it, style it, never delete it:
.btn:focus-visible {
  outline: 2px solid #0d9488;   /* teal */
  outline-offset: 2px;
}

// Icon-only button? Give it an accessible NAME or it announces nothing:
<button onClick={handleClose} aria-label="Close dialog">
  <XIcon aria-hidden="true" />
</button>

The <button> is focusable, keyboard-operable, and announced as "Checkout, button", with zero extra code. Two things worth calling out: never delete the focus outline (use :focus-visible to style it instead of removing it), and an icon-only button needs an accessible name via aria-label, because there is no visible text for the accessibility tree to use. Mark the decorative icon aria-hidden="true" so it is not announced twice.

The first rule of ARIA

The first rule of ARIA is: don't use ARIA. If a native HTML element gives you the role and behavior you need, use it instead.
WCAG ARIA Authoring Practices

ARIA, Accessible Rich Internet Applications, is a set of role, aria-*, and state attributes that *describe* elements to assistive tech. The catch: ARIA changes only what is announced, never how the element behaves. Adding role="button" to a div makes a screen reader *say* "button," but it does not make Tab stop on it, and it does not make Enter fire the handler. You still have to build all of that yourself, and forget one piece, and you have shipped a control that *claims* to be a button but is broken. A real <button> is honest by construction.

So when *is* ARIA the right tool? When HTML genuinely has no element for what you are building: a tabbed interface, a custom autocomplete combobox, a tooltip, a aria-live region that announces async updates. There ARIA is essential, but follow a recipe (the ARIA Authoring Practices patterns), do not improvise. The rule of thumb: reach for the HTML element first, and only add ARIA when you have proven nothing native fits.

Bad ARIA is worse than no ARIA

An incorrect role or a stale aria-state actively lies to screen-reader users. `<button role="link">` or an `aria-expanded` that never updates is more confusing than plain markup. If you are not sure the ARIA is correct and kept in sync, leave it off.

Common mistakes that lock people out

  1. Div buttons and div links. <div onClick> is not focusable or keyboard-operable. Use <button> for actions and <a href> for navigation, the choice signals intent to the accessibility tree.
  2. Deleting focus styles. outline: none with no replacement leaves keyboard users with no idea where they are. Style :focus-visible instead, never remove it.
  3. Low color contrast. Grey-on-white that looks sleek can fail WCAG. Body text needs a contrast ratio of at least 4.5:1 (3:1 for large text). Check it with your DevTools or a contrast tool.
  4. Color as the only signal. "Required fields are red" is invisible to colorblind users. Pair color with an icon, text, or an asterisk.
  5. Images without alt text. Every meaningful <img> needs alt describing its content. Decorative images get alt="" (empty, not missing) so screen readers skip them.
  6. Misused or stale ARIA. Inventing roles, or an aria-expanded / aria-pressed that never updates, lies to users. Prefer native HTML; keep any ARIA state in sync with reality.
  7. Inputs with no label. A placeholder is not a label, it vanishes on typing and is poorly announced. Wire every input to a real <label> (or aria-label).

Takeaways

The whole article in seven lines

  • Accessibility is usability for everyone, and mostly free if you start with semantic HTML.
  • Assistive tech reads the **accessibility tree** (role · name · state), not your CSS.
  • Use the right element: `<button>` for actions, `<a href>` for links, `<label>` for inputs, headings for structure.
  • POUR: Perceivable, Operable, Understandable, Robust, the four questions every UI must answer.
  • Keep focus visible (`:focus-visible`), hit 4.5:1 contrast, and never use color as the only signal.
  • First rule of ARIA: don't use ARIA, it changes announcements, not behavior. Reach for HTML first.
  • Test it: unplug the mouse, tab through, zoom to 200%, run a screen reader.

Where to go next

Accessibility is a thread that runs through everything else on the Frontend Engineer path, it is not a separate module so much as a quality bar for all the UI you build. Two sibling articles connect directly to it.

Then practice the pocket test on your own app: unplug the mouse, tab through a full flow, and fix the first thing that traps or hides your focus. That one habit will teach you more than any checklist.

Want to go deeper?

This article covers concepts taught hands-on in the Cloud Engineer and DevOps career paths, with real terminal labs, production scenarios, and structured lessons.