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.