Concepts

Semantic HTML

Using HTML elements for their meaning, not just their default appearance. A `<button>` announces as a button; a `<div onclick>` announces as nothing. The vast majority of accessibility failures trace back to abandoned semantics.

Semantic HTML is HTML where each element is chosen for its meaning, not just its appearance. A <button> is for actions. A <nav> wraps navigation. A <table> holds tabular data. Using a <div> styled to look like a button for an action is a semantic failure — even if the visual result is identical.

The vast majority of accessibility bugs trace back to abandoned semantics. Almost every assistive-tech failure has the same root cause: a generic element doing the job a semantic one was designed for.

Why screen readers care

Screen readers expose HTML to the user through their accessibility tree — an internal representation built from the DOM that maps every element to a role, name, state, and properties. The accessibility tree is what the user actually hears.

A native <button> enters the accessibility tree as button "Submit". A <div> styled to look like a button enters as generic — meaning the screen reader announces nothing useful, and the user has no way to discover that the div is interactive.

The same logic applies to:

  • Headings (<h1><h6>) — screen readers let users jump between headings to skim a page. <div class="heading"> doesn’t appear in the heading list.
  • Lists (<ul>, <ol>, <li>) — screen readers announce “list, 5 items” before reading. <div>s pretending to be list items don’t.
  • Forms — <input> with a connected <label> reads “Email, edit text.” A custom input made of divs and contenteditable reads nothing.
  • Tables — <table> with <th> row/column headers lets users navigate data tables cell-by-cell with header context announced. CSS grids of divs don’t.

The replacement pattern

The fix is almost always the same shape:

  • <div onclick="..."><button> (or <a href> for navigation).
  • <span class="link"><a href>.
  • <div role="heading"><h1> through <h6>.
  • Custom dropdowns → <select> where the design allows, or a pattern-tested ARIA combobox where it doesn’t.
  • Tabs constructed as styled <div>s → tab pattern from the ARIA Authoring Practices Guide.

When to reach for ARIA

The first rule of ARIA is: don’t use ARIA if a native element does the job. ARIA roles, states, and properties are a patch for cases where native HTML cannot describe the widget you’re building (treeviews, multi-select comboboxes, certain modal patterns). They are not a substitute for using <button>.

A pragmatic audit

The fastest way to detect semantic decay in a codebase: search for onclick on div and span. Every match is a candidate failure. The next-fastest: search for role= attributes. ARIA is fine in limited contexts, but heavy ARIA usage often signals that native elements were skipped for styling reasons that didn’t actually require the skip.