When developers ask me about accessibility, they usually ask about ARIA attributes. They rarely ask about the correct HTML element. That’s the wrong order – and one of the most common reasons why custom components fail audits. Semantic HTML isn’t just some old-fashioned detail from before the framework era; it’s the foundation of every accessible website. If you choose the right element, you get keyboard navigation, focus behaviour, screen reader support and form integration as standard. Those who ignore it divand work with building blocks plus ARIA end up laboriously recreating the same functionality – usually incompletely. In this guide, I’ll show you, using specific code comparisons, why semantic HTML renders around 90 per cent of all ARIA attributes superfluous.
What defines semantic HTML
Semantic HTML means choosing elements not based on their appearance, but on their meaning. A button is a `<button>button`, a list is a `<ul>list` or `<ol>ul`, a main heading is a `<h1>h1` and not a styled `<div>p`. This meaning is invisible to sighted users, but is crucial for assistive technologies: a screen reader distinguishes between a button and a paragraph, navigates via headings and landmarks, and announces lists as such. Generic containers such as <div> or <span>say nothing about the function of their content – they are mere containers with no semantic value.
Why semantic HTML makes 90 per cent of ARIA attributes redundant
This observation is not my own, but is stated in a similar vein in the W3C’s ARIA Authoring Practices: if a native HTML element provides the necessary semantics and behaviour, use that element instead of ARIA. Seven specific comparisons illustrate what this means in practice.
Button
<!-- Mit ARIA -->
<div role="button" tabindex="0"
onclick="speichern()"
onkeydown="if(event.key==='Enter')speichern()">
Speichern
</div>
<!-- Semantisch -->
<button onclick="speichern()">Speichern</button>
The native <button> element provides the following by default: focusability, keyboard navigation using the Enter and Spacebar keys, default focus style, correct role for screen readers, and form integration. With the div-construct, you have to implement all of this yourself – and usually end up overlooking at least one aspect.
Navigation
<!-- Mit ARIA -->
<div role="navigation" aria-label="Hauptnavigation">
<div><a href="/">Start</a></div>
<div><a href="/produkte">Produkte</a></div>
</div>
<!-- Semantisch -->
<nav aria-label="Hauptnavigation">
<ul>
<li><a href="/">Start</a></li>
<li><a href="/produkte">Produkte</a></li>
</ul>
</nav>
The `landmark<nav>` element is a landmark that screen reader users can navigate to using a single keyboard shortcut. The `count<ul>` attribute announces the number of items. The `divdiv`-based approach cannot easily achieve this with ARIA – and even then, the list announcements are missing.
Accordion
<!-- Mit ARIA (vereinfacht) -->
<div role="button" aria-expanded="false"
aria-controls="panel-1" tabindex="0">
Details
</div>
<div id="panel-1" role="region" hidden>
Inhalt
</div>
<!-- Semantisch -->
<details>
<summary>Details</summary>
Inhalt
</details>
The pair of elements<details> and<summary> is a fully functional native accordion – complete with keyboard controls, toggle behaviour and state notifications. Anyone who isn’t aware of this ends up recreating the component in JavaScript, often forgetting to include the aria-expanded-update.
Practical tip: You can find out how your own website is performing in just a few minutes with Access Score’s free BFSG Check – no registration required, and with an honest assessment based on WCAG 2.2.
Lists
<!-- Falsch -->
<div class="item">Erstes Element</div>
<div class="item">Zweites Element</div>
<div class="item">Drittes Element</div>
<!-- Semantisch -->
<ul>
<li>Erstes Element</li>
<li>Zweites Element</li>
<li>Drittes Element</li>
</ul>
A screen reader announces the <ul>list as ‘a list with three items’ and allows the user to navigate between the list items. The divalternative version sounds like three unrelated sentences.
Heading structure
<!-- Falsch (rein optisch) -->
<div class="title-large">Unsere Produkte</div>
<div class="title-medium">Kategorie A</div>
<!-- Semantisch -->
<h1>Unsere Produkte</h1>
<h2>Kategorie A</h2>
The heading hierarchy is to screen reader users what skimming is to sighted people – the only way to quickly get their bearings. They can use keyboard shortcuts to navigate between headings. Without proper H elements, this navigation is completely lost.
Form Labels
<!-- Falsch -->
<span>E-Mail-Adresse</span>
<input type="email">
<!-- Semantisch -->
<label for="email">E-Mail-Adresse</label>
<input type="email" id="email">
The linked<label> element ensures that clicking on the label text focuses on the field – important for users with motor impairments. A screen reader automatically reads out the label when the field is focused. The span-construction lacks both of these properties.
Images
<!-- Falsch -->
<div style="background-image: url(produkt.jpg)"
aria-label="Produktfoto"></div>
<!-- Semantisch -->
<img src="produkt.jpg" alt="Produktfoto">
An <img>element with a valid altattribute is correctly announced as an image by screen readers, loads as expected, and can be viewed or downloaded using browser tools. A photograph loaded as a background image is simply invisible for many of these purposes.
An overview of the most important semantic elements
A concise overview, grouped by function:
| Category | Elements | Function |
|---|---|---|
| Structure (Landmarks) | header, nav, main, aside, footer | Main sections of the page, navigable via screen reader shortcuts |
| Content sections | article, section | Subdivision of content |
| Headings | h1 to h6 | Hierarchical structure |
| Lists | ul, ol, li, dl, dt, dd | Related lists |
| Interactive elements | button, a, input, select, textarea, label, details, summary, dialog | Native interaction with built-in accessibility |
| Tables | table, thead, tbody, tr, th, td, caption | Tabular data with cell mapping |
| Media | img, figure, figcaption, video, audio | Embedded content with alternatives |
Landmarks: How screen reader users navigate
<header><nav><main><aside><footer> <div>-As it is contained within a container, it forces the user to listen through the content sequentially. Important: it<main> must only appear once per page and identifies the actual main content – the content to which a skip link at the top of the page should lead. I have described how keyboard navigation and focus management work in general in the article ‘Implementing Keyboard Navigation and Focus Management in accordance with WCAG’.
When you really still need ARIA
The remaining ten per cent. If your HTML is semantically sound, ARIA is reserved for a small but important set of cases:
- Icon buttons without visible text– a magnifying glass icon requires one
aria-label, as the element itself has no text content. - Custom widgets for which HTML has no equivalent – tabs, carousels, trees, complex modals. Here, too, the rule applies: check first whether
<details>, or<dialog>other native elements might be suitable. - Live regions – toast notifications, dynamic search results, status messages that need to appear without the focus shifting.
- Status information for non-native components –
aria-expanded,aria-pressed,aria-current.
You can find out which ARIA patterns are correct and which have the opposite effect in Using ARIA Labels Correctly: The Practical Guide for Developers.
The most common HTML anti-patterns in practice
There are four patterns that I notice most frequently during code reviews: Firstly, <div>‘soup’ – an entire page consisting solely of nested generic containers, without a single semantic element. Secondly, links that are meant to look like buttons but appear as <a> withouthref or with href="#" – a <a> without a target is semantic rubbish. Thirdly, tables used for layout purposes – an anti-pattern that has survived from the 2000s and is still rife in newsletter templates. Fourthly, multiple <h1>-Elements on a single page, either because the page builder sets it up that way or because different components each mark their headings as ‘main’. One per page<h1> – that’s the unwritten rule that is rarely followed in audits.
What to look out for in a code review
HTML issues are the only type of accessibility violation that can be reliably detected automatically – missing altattributes, incorrect heading hierarchy, generic containers lacking semantic meaning, and buttons without labels. This is exactly where our Access Guard comes in: the monitoring system automatically checks your pages for the typical HTML anti-patterns outlined in this guide after every code deployment and reports them directly to your development tools such as Jira, Slack or Teams. It provides specific guidance on which semantic element would be correct in each instance.
This is no substitute for a code review – but it does speed up the code review process, as the obvious issues have already been flagged before anyone looks at the code. If you want to get a rough idea of the current state of your website, start with the free Access Score.
Does your team want to fix issues in the code rather than just covering them up? In 30 minutes, we’ll explain how testing and fixing fit into your day-to-day development work.
