Accessibility failures are design decisions. Here is the proof. opened with a stat that should stop anyone building interfaces: pages that use ARIA average 34% more accessibility errors than pages that do not touch it at all. That piece covered why in one paragraph: nobody specified the behavior, so the attribute got implemented as a guess. This article covers the rest: what ARIA actually is, the one rule that prevents most of that damage, the handful of patterns a product designer will actually run into, and how to document interactive behavior so a developer implements it once, correctly, instead of guessing.

01

What ARIA actually is

1.1 Roles, states, and properties

ARIA attributes fall into three categories, and confusing them is where most misuse starts.

Roles tell assistive technology what an element is. role="dialog", role="tablist", role="alert". A role replaces or adds to an element's default semantics: a <div> with role="button" is announced as a button, even though it has none of a button's actual behavior.

States describe a condition that changes during use. aria-expanded="true", aria-checked="false", aria-disabled="true". States are meant to change (that is the entire point of them), and a state that never updates after the interface changes is worse than no state at all, because it tells the user something false.

Properties describe a relationship or a characteristic that mostly stays fixed. aria-label, aria-labelledby, aria-describedby, aria-controls. Properties tell assistive technology what an element is connected to, or what to call it when it has no visible text.

Roles

role="dialog" role="tablist" role="alert"

What the element is.

States

aria-expanded aria-checked aria-disabled

What changes during use.

Properties

aria-label aria-describedby aria-controls

What it's connected to.

Three categories. Most confusion about ARIA is a confusion between these three.

1.2 The first rule of ARIA

The W3C's own authoring guidance opens with what practitioners call the first rule of ARIA: if a native HTML element already has the semantics and behavior you need, use it instead of rebuilding it from a styled <div> plus a role attribute.

The reason is not stylistic. A native <button> comes with keyboard activation on Enter and Space, a visible focus state, the correct role announced automatically, and correct form-submission behavior when it sits inside a <form>. role="button" on a <div> gives you exactly one of those things: the announced role. Nothing else follows automatically. Keyboard handling and focus management all have to be built and maintained by hand, and it is trivially easy to forget one of them. This is the same story as the div-vs-button example from the previous piece in this series: ARIA is frequently reached for to patch a component-choice problem that a native element would have solved for free.

1.3 What ARIA cannot do

This is the point most designers miss: ARIA changes what is announced. It does not change what happens. Adding aria-expanded="true" to a div does not make Enter or Space open anything. A developer still has to wire that behavior up separately, in JavaScript, regardless of what the markup says. ARIA is a labeling layer sitting on top of behavior that has to exist independently of it. That is exactly why documenting the behavior, covered in section 4, matters as much as knowing the attribute name.

02

The patterns designers actually touch

2.1 Modals and dialogs

A modal needs role="dialog", aria-modal="true", and aria-labelledby pointing at whatever text serves as its heading. That part is quick. The part that determines whether it actually works is the behavior around it, and that is where the designer's decisions live: what triggers it, where focus lands when it opens, whether Tab and Shift+Tab loop inside it or leak out to the page behind it, what closes it, and where focus goes when it does.

Delete project?

This cannot be undone. All data will be permanently removed.

Delete
  1. Trigger: click on "Delete project" in the project menu
  2. On open: focus moves to the dialog heading, not the first button
  3. While open: Tab and Shift+Tab loop within the dialog only
  4. Closes on: Escape key, backdrop click, or either button
  5. On close: focus returns to the menu item that opened it, not the page body

This is what a designer specifies. A developer turns this directly into the correct ARIA and focus-management code.

2.2 Custom dropdowns and comboboxes

A native <select> needs zero ARIA. It has a role, full keyboard support, and screen reader announcement built into every browser, for free. The moment a dropdown is restyled beyond what <select> allows and rebuilt from <div>s, it needs a working set of six or more attributes just to reach parity: role="combobox" on the trigger, aria-expanded reflecting open state, aria-controls pointing at the listbox, role="listbox" on the options container, role="option" on each item, and aria-activedescendant tracking which option currently has virtual focus as arrow keys move through the list. None of that is required by the native element. Every one of those attributes has to stay perfectly in sync with the actual keyboard behavior, or the mismatch is worse than doing nothing.

Native <select>

Notifications
role: automatic keyboard: automatic announcement: automatic

0 ARIA attributes needed

Custom dropdown

Notifications
role="combobox" aria-expanded aria-controls role="listbox" role="option" aria-activedescendant

6 ARIA attributes needed, all kept in sync by hand

Same interaction, same visual result achievable either way. Only one of them requires ongoing maintenance to stay correct.

2.3 Live regions

Some interface changes need to be announced without moving the user's focus: a toast confirming a save, a validation summary appearing above a form, a loading state resolving. aria-live="polite" on the container that receives that content tells screen readers to announce it once the user's current action finishes, without interrupting and without stealing focus. aria-live="assertive" interrupts immediately: reserve it for things that block the task, like a session-timeout warning, not for routine confirmations.

The mistake I see most often: a toast that renders with a CSS animation and reads perfectly to a sighted user, wired to nothing. No live region, no role="status". Visually it works exactly as designed. For a screen reader user, the confirmation never happened. The save may as well have failed silently.

Visual only

✓ Changes saved
Not announced

aria-live="polite"

✓ Changes saved
Announced on save

Identical toast. One exists for screen reader users, the other only exists on screen.

2.4 Disclosure and accordions

aria-expanded on the trigger button, aria-controls pointing at the panel's id. Simple, cheap, and one of the most commonly skipped attributes on the web: the visual expand and collapse works perfectly, the announcement of that state to a screen reader is the part that gets left out under deadline.

03

Why ARIA misuse is worse than no ARIA at all

3.1 The 34% stat, unpacked

WebAIM's Million report has tracked this for years: home pages with ARIA present carry meaningfully more detected accessibility errors, on average, than home pages with none. The reason is structural, not a coincidence of who happens to use ARIA. ARIA overrides native semantics unconditionally. Once role="button" sits on an element, screen readers trust it completely, even if the underlying element is a link, an image, or plain text with a click handler taped on. A wrong or stale ARIA attribute does not just fail to help. It actively misinforms the one channel of information a screen reader user has for that element. That is structurally different from a missing alt attribute, which is merely silent. Silence prompts a screen reader to fall back to something else. A false statement does not.

3.2 The three failure patterns worth knowing by sight

Redundant roles. role="button" on an actual <button>. Harmless on its own, but a reliable signal that nobody checked the fundamentals before adding ARIA on top of them.

Conflicting or stale state. aria-expanded="false" hardcoded in the markup and never updated by JavaScript when the panel actually opens. The user hears "collapsed" while looking at, or unable to see, a panel that is fully open.

aria-hidden on the wrong branch. aria-hidden="true" applied to a container that still holds focusable children inside it. The content disappears from the accessibility tree, but a keyboard user can still tab into elements a screen reader user is told do not exist, an inconsistency between two ways of navigating the same page.

04

Documenting behavior so developers implement it right

4.1 What to specify

Not attribute names. Behavior. For every interactive component, five questions cover almost every case: what triggers the state change, what the initial state is, where focus goes on open and on close, what the keyboard is expected to do (Escape, arrow keys, Tab boundaries), and what should be announced when the state changes, described in plain language rather than markup.

A developer who receives "opens on click, focus moves to the dialog heading, Escape and a backdrop click both close it, focus returns to the trigger" can implement the correct ARIA without the designer ever writing an attribute name. A developer who receives only the visual mockup has to guess. Guessing under a deadline is where the 34% comes from.

4.2 Keep the notes attached to the component, not in a separate document

Behavior notes that live in a shared "interactions" document get skipped, because nobody opens a second file mid-build. Attach them directly to the frame or component in whatever tool the handoff happens through: a sticky note, a dev-mode annotation, a comment thread pinned to the component itself. The closer the documentation sits to the artifact a developer is already looking at, the higher the odds it survives the handoff intact.

05

Testing what you shipped

5.1 A two-minute check any designer can run

No screen reader license required. Chrome DevTools → Elements → the Accessibility pane shows the computed role, name, and state of any selected element in real time. Open a modal, select it in DevTools, confirm the role reads "dialog" and the name matches the heading you expect. On Mac, VoiceOver ships free with the OS (Cmd+F5 toggles it); walk your own component with it once before it ships. On Windows, NVDA is free to install. None of this replaces a full audit. All of it catches the failures in section 3 before a developer has to.

5.2 ARIA cannot fix a decision made upstream

The correct amount of ARIA on a well-chosen component is often close to zero: a native element already carries most of it. The ARIA required to patch a badly-chosen one keeps growing, and every attribute added is one more place for the implementation to drift from the design over time. Accessibility failures are design decisions covers the component-choice failures this compounds with: the wrong element chosen at the design stage, multiplied by every developer who has to work around it afterward. The fix in both cases is the same: the decision that matters happens before a single attribute gets written, in the file the designer controls.

Sources