Skip to content
C Codeloom
HTML & CSS

HTML, ARIA, and Screen Reader Tips

Practical guidance on using semantic HTML and ARIA correctly so screen reader users get a good experience without you over-engineering markup.

·5 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • When semantic HTML is enough on its own
  • When ARIA roles and attributes actually help
  • The first rule of ARIA and why it matters
  • How screen readers announce common patterns
  • Quick checks you can run before shipping

Prerequisites

  • Comfortable with HTML

Accessibility work has a reputation for being intimidating, but most of it boils down to two ideas: use the right HTML element, and only reach for ARIA when the right element does not exist. This article focuses on the practical day-to-day decisions that determine whether a screen reader user can actually use what you build.

What ARIA is and why it matters

ARIA stands for Accessible Rich Internet Applications. It is a set of attributes that fill the gaps where HTML alone cannot describe the role or state of a custom widget. ARIA does not change behavior, only what assistive technology hears. If you build a fake button out of a div, ARIA can tell a screen reader it is a button, but it cannot make it focusable or keyboard operable. You still have to add those yourself.

This matters because the web is increasingly built from custom components. Tabs, comboboxes, modals, and toolbars are not native HTML elements. Without ARIA, screen reader users hear “clickable” or nothing at all.

Mental model

The first rule of ARIA, straight from the spec, is: do not use ARIA. If a native element does the job, use it. Native elements come with role, state, keyboard behavior, and focus management for free. ARIA is for the cases where no native element fits.


Does a native HTML element fit?
      |
 yes  +--> use it. done.
      |
 no   +--> can you compose native elements?
              |
         yes  +--> compose. add minimal ARIA only if needed.
              |
         no   +--> build with div + role + state + keyboard
Decision flow for choosing semantics

The mental model is a layered cake. Semantic HTML is the bottom, broadest layer. ARIA roles refine semantics where HTML cannot. ARIA states (aria-expanded, aria-selected, aria-checked) describe dynamic state. Focus management ties it all together.

Hands-on example

Suppose you are building a disclosure (a button that shows or hides a panel). Native HTML now has a details and summary pair that does this, but let us assume you need custom styling that pushes you to a button.

<button
  type="button"
  aria-expanded="false"
  aria-controls="faq-1"
  id="faq-trigger-1">
  How do I cancel my subscription?
</button>
<div id="faq-1" hidden>
  Visit your account page and click Cancel.
</div>
const btn = document.getElementById('faq-trigger-1')
const panel = document.getElementById('faq-1')
btn.addEventListener('click', () => {
  const open = btn.getAttribute('aria-expanded') === 'true'
  btn.setAttribute('aria-expanded', String(!open))
  panel.hidden = open
})

A screen reader announces this as: “How do I cancel my subscription, button, collapsed.” When the user activates it, the script flips aria-expanded to true and unhides the panel. The next announcement says “expanded.”


initial   : "How do I cancel, button, collapsed"
on click  : "expanded"
re-focus  : "How do I cancel, button, expanded"
on click  : "collapsed"
What a screen reader announces at each step

Notice we used a real button element. We did not slap role=“button” on a div. The button gives us focus, Enter and Space activation, and the disabled attribute for free. ARIA only describes the expand state because HTML has no native attribute for that on a button.

Common pitfalls

The most common pitfall is adding role=“button” to a div without also adding tabindex, keyboard handlers, and Space activation. The screen reader hears “button” but the keyboard user cannot reach it. Always prefer a real button.

A second pitfall is over-labeling. aria-label is loud. If you label a button “Save” but it already contains visible text “Save,” you can end up with double announcements or, worse, with the visible text being ignored. Use aria-label only when there is no visible label.

A third is hiding content the wrong way. display: none and the hidden attribute remove an element from the accessibility tree. visibility: hidden does the same. But CSS tricks like opacity: 0 or moving the element off-screen leave it discoverable. Pick the right hiding technique for your intent.

A fourth is broken focus management in modals. When a modal opens, focus should move into it. When it closes, focus should return to the trigger. Without this, screen reader users find themselves stranded at the top of the page, with no idea what just happened.

Best practices

Run through the page with the Tab key. Every interactive element should be reachable, focus should be visible, and the order should match what you see. If you cannot use the page without a mouse, neither can many users.

Test with a real screen reader at least occasionally. VoiceOver on macOS, NVDA on Windows, and TalkBack on Android are all free. You do not need to be an expert. Just listening to a page tells you a lot.

Use landmarks. header, nav, main, and footer give screen reader users a quick way to jump around. A page with no landmarks forces them to read top to bottom.

Write meaningful link text. “Click here” tells a screen reader user nothing when they list all links on the page. “Read the pricing guide” tells them exactly where the link leads.

Keep ARIA minimal. Every attribute is a promise you have to keep up to date. If aria-expanded says false when the panel is open, you have actively made the experience worse than no ARIA at all.

Wrap-up

Accessibility is not a separate skill set bolted on at the end. It is a side effect of clear, semantic markup and small, careful additions where HTML runs out of vocabulary. Use the right element first. Reach for ARIA only when you must. Manage focus deliberately. Run the page with a keyboard. Do those four things and your interfaces will be usable by a much wider audience than you might think.