TL;DR: ARIA (Accessible Rich Internet Applications) attributes give screen readers names and descriptions for elements that native HTML can’t fully describe. The three you’ll use most are
aria-label(provides an accessible name from a string),aria-labelledby(points to visible text that serves as the name), andaria-describedby(points to supplementary text like a hint or error). But the first rule of ARIA is don’t use ARIA when a native HTML element does the job — a real<button>beats a<div role="button">every time. This guide shows correct ARIA for the components on every government site: nav menus, search, icon buttons, alerts, modals, accordions, breadcrumbs, and skip links — plus the misuses that make things worse.
Most accessibility problems on government websites are not solved by ARIA — they’re solved by writing better HTML. But a handful of common components genuinely need ARIA to be accessible, and government sites get these wrong constantly: an icon-only “search” button with no name, a “main navigation” that’s indistinguishable from a “footer navigation,” a modal dialog that screen readers wander right out of. Adding the right ARIA labels is often the difference between a component that’s usable and one that announces “button, button, button” with no meaning.
This guide covers the three labeling attributes you’ll actually use and shows correct, copy-able code for the components that appear on nearly every city, county, agency, and university site. It also flags the misuses that fail WCAG 2.2 AA — because broken ARIA is worse than no ARIA, and ARIA misuse is one of the top accessibility failures auditors find.
The First Rule of ARIA: Don’t Use ARIA
The W3C’s own authoring guidance opens with this: “If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.”
ARIA changes how assistive technology describes an element. It does not add behavior. A <div role="button"> has the button role, but it won’t respond to Enter or Space, won’t be in the tab order, and won’t fire on keyboard activation unless you write all of that JavaScript yourself. A native <button> does all of it for free.
So before reaching for ARIA, ask: is there a native element? Use <button>, not <div onclick>. Use <nav>, not <div class="nav">. Use <a href> for links, <input> with a <label> for fields. ARIA is for the gaps native HTML leaves — naming an icon button, identifying which <nav> is which, wiring up a custom widget. The WebAIM Million report has consistently found that pages with ARIA present average more detected errors than pages without it, precisely because ARIA is so often misapplied. Use it deliberately.
The three attributes you’ll use most
aria-label="..."sets the accessible name directly from a string. Use it when there’s no visible text to reference (an icon-only button). The string is not visible on screen.aria-labelledby="id1 id2"sets the accessible name from one or more visible elements, by ID. Prefer this when the name is already on the page — it stays in sync and is translated like the rest of the page.aria-describedby="id"adds a description announced after the name — a hint, a format example, an error message. It supplements the name; it doesn’t replace it.
A key gotcha: aria-label and aria-labelledby override the visible text of an element for screen reader users. If a button says “Submit” but has aria-label="Send", screen reader users hear “Send” — and voice-control users saying “click Submit” may fail. Don’t override visible text unless you mean to.
Navigation Menus
A government site often has several <nav> regions — primary, utility, footer, breadcrumb. Screen reader users can list all “navigation” landmarks at once, so if they’re all named “navigation” they can’t tell them apart. Give each a distinct accessible name with aria-label:
<nav aria-label="Primary">
<ul>
<li><a href="/services">Services</a></li>
<li><a href="/departments">Departments</a></li>
<li><a href="/government">Government</a></li>
</ul>
</nav>
<nav aria-label="Footer">
<ul>
<li><a href="/privacy">Privacy</a></li>
<li><a href="/accessibility">Accessibility</a></li>
</ul>
</nav>
Don’t include the word “navigation” in the label — the role already announces it, so aria-label="Primary navigation" produces “Primary navigation navigation.” Just aria-label="Primary".
For a “you are here” current page, use aria-current="page" on the active link, not a class alone:
<a href="/services" aria-current="page">Services</a>
Search
A search box with no visible label needs an accessible name. If there’s no on-screen “Search” text, label the input directly, and label the landmark region:
<form role="search" aria-label="Site search" action="/search">
<label for="site-search" class="visually-hidden">Search this site</label>
<input type="search" id="site-search" name="q"
placeholder="Search permits, services, departments">
<button type="submit">
<svg aria-hidden="true" focusable="false"><!-- magnifier icon --></svg>
<span class="visually-hidden">Search</span>
</button>
</form>
Two patterns here are worth copying everywhere: a visually hidden <label> (a .visually-hidden class that’s off-screen but still read by screen readers) is preferable to aria-label when you can use it, and the icon is hidden with aria-hidden="true" while a visually hidden <span> carries the real name. Don’t rely on the placeholder as the label — it isn’t a reliable accessible name and vanishes on input.
Icon-Only Buttons
Buttons with only an icon and no text are the single most common place government sites need aria-label. A close button, hamburger menu, print, share, or “expand” control must announce what it does:
<button type="button" aria-label="Close dialog">
<svg aria-hidden="true" focusable="false"><!-- X icon --></svg>
</button>
<button type="button" aria-label="Open menu" aria-expanded="false"
aria-controls="main-menu">
<svg aria-hidden="true" focusable="false"><!-- hamburger icon --></svg>
</button>
Note aria-expanded on the menu toggle — it tells screen reader users whether the menu is open or closed, and you must update it to "true" in JavaScript when the menu opens. aria-controls points to the element being toggled. The icon SVG gets aria-hidden="true" and focusable="false" so it isn’t announced separately or tab-focused in some browsers.
Alerts and Status Messages
When content appears or changes without a page reload — a “Your application was submitted” confirmation, a form error, a session-timeout warning — screen reader users won’t notice unless you use a live region. The native roles are role="alert" (assertive, interrupts immediately, for errors and urgent messages) and role="status" (polite, waits for a pause, for confirmations):
<!-- Urgent: announced immediately -->
<div role="alert">
Your session will expire in 2 minutes. Select "Continue" to stay signed in.
</div>
<!-- Polite: announced when the user is idle -->
<div role="status">
Payment received. Confirmation number 2026-44871.
</div>
The element must exist in the DOM before you insert text into it for the announcement to fire reliably. Don’t overuse role="alert" — reserve assertive announcements for things that genuinely warrant interrupting the user.
Modal Dialogs
Custom modal dialogs are where ARIA gets involved and where it’s easy to get wrong. The cleanest path is the native <dialog> element with showModal(), which handles focus trapping and the backdrop for you. If you build a custom one, you need the dialog role, a label, focus management, and Escape to close:
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title"
id="confirm-dialog">
<h2 id="dialog-title">Confirm permit submission</h2>
<p>Once submitted, your building permit application cannot be edited.</p>
<button type="button">Go back</button>
<button type="button">Submit application</button>
</div>
aria-labelledby points to the dialog’s visible heading, so the screen reader announces “Confirm permit submission, dialog.” aria-modal="true" tells assistive tech that content outside the dialog is inert. You still must, in JavaScript: move focus into the dialog when it opens, trap Tab inside it, close on Escape, and return focus to the triggering button when it closes. ARIA names the dialog — it doesn’t manage focus.
Accordions and Expand/Collapse
Accordions (FAQ lists, expandable “more info” sections) are everywhere on government sites. Build each header as a real <button> with aria-expanded and aria-controls:
<h3>
<button type="button" aria-expanded="false" aria-controls="faq-1-panel"
id="faq-1-button">
How do I renew a business license?
</button>
</h3>
<div id="faq-1-panel" role="region" aria-labelledby="faq-1-button" hidden>
<p>Business licenses can be renewed online through the licensing portal...</p>
</div>
The button’s text is its accessible name (no aria-label needed). aria-expanded flips to "true" when open; the panel’s hidden attribute is removed. The panel uses aria-labelledby to borrow the button’s text as its region name. Using a native <button> means keyboard activation works automatically.
Breadcrumbs
Wrap the breadcrumb trail in a <nav> with a label and mark the current page:
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/services/permits" aria-current="page">Permits</a></li>
</ol>
</nav>
The aria-label="Breadcrumb" distinguishes this navigation from the others, and aria-current="page" marks the last item so screen reader users know it’s the current location.
Skip Links
A “Skip to main content” link lets keyboard users jump past the repeated navigation. It’s a native anchor — no ARIA needed — but it needs a target with a matching id, and it should become visible on focus:
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- ...header and nav... -->
<main id="main-content" tabindex="-1">
<!-- page content -->
</main>
The tabindex="-1" on <main> ensures focus actually lands there in all browsers when the link is activated. Skip links are required to satisfy SC 2.4.1 Bypass Blocks and are a small, high-value addition for keyboard accessibility.
Common ARIA Misuses to Avoid
These patterns appear constantly on government sites and actively harm accessibility:
role="button"on a<div>instead of a<button>. You lose keyboard operability, focus, and activation. Use<button>.aria-labelon an element that already has visible text — it silently overrides the visible label and breaks voice control. Only label elements that lack a visible name.aria-hidden="true"on a focusable element. This hides it from screen readers while it’s still tab-focusable, creating “phantom” focus stops. Never putaria-hiddenon something interactive.- Redundant roles like
<nav role="navigation">or<button role="button">. The native element already has the role; the duplicate is noise. aria-labelon non-interactive, non-landmark elements (a plain<div>or<span>). It’s often ignored entirely. Labels belong on interactive controls and landmark regions.- Decorative
aria-labels in the wrong language.aria-labeltext is read by the screen reader but not translated by the page’s translation widget the way visible text is — another reason to preferaria-labelledbyto visible content when possible.
A reliable habit: build the component with plain semantic HTML first, test it with a keyboard and a screen reader (NVDA on Windows, VoiceOver on macOS), and add ARIA only to fill the specific gaps you observe. If you can’t explain what a given ARIA attribute is doing for a real user, remove it.
ARIA labeling is one piece of a complete WCAG 2.2 AA effort. Automated tools catch some ARIA errors — a role with a missing required attribute, an aria-labelledby pointing at a nonexistent ID — but they can’t tell you whether a name actually makes sense to a human. That’s why every ARIA pattern above should be verified in a manual accessibility audit with real assistive technology.
Correct ARIA is quiet: it makes a component announce itself clearly and then gets out of the way. The danger is that ARIA is easy to add and easy to break, and a single bad attribute can silence an entire control without throwing any visible error. Because government sites are edited by many hands, ARIA regressions creep in with every template tweak and new component. Govzu continuously scans your government website for ARIA and labeling failures — flagging a broken aria-labelledby reference or an unnamed icon button the moment it ships, so the accessible components you build today stay accessible tomorrow.