Why Use Semantic HTML (Even When Automated Tests Pass)
Overview
Your accessibility tests might show zero errors, but that doesn’t mean your site is truly accessible. Automated testing tools can only detect a fraction of accessibility issues. If your pages are built with generic <div> and <span> elements propped up with ARIA attributes, you’re missing out on the built-in accessibility, keyboard behavior, and cross-device support that native HTML elements provide for free.
This guide explains why semantic HTML should be your foundation for accessibility — not an afterthought.
The First Rule of ARIA
The W3C’s rules of ARIA use state:
If you can use a native HTML element with the semantics and behavior you require already built in, instead of repurposing an element and adding an ARIA role, state, or property to make it accessible, then do so.
ARIA (Accessible Rich Internet Applications) was designed as a bridge — a way to fill gaps where HTML didn’t yet have the right element. It was never meant to replace semantic HTML. Think of ARIA as a polyfill: useful when nothing else works, but ideally replaced by the real thing as browsers evolve.
Native HTML Elements vs ARIA
Many developers reach for ARIA attributes out of habit when a native HTML element would do the job better. Here’s a reference for common patterns:
Buttons and Controls
<!-- Avoid: ARIA on a generic element -->
<div role="button" tabindex="0" onclick="handleClick()"
onkeydown="if(event.key==='Enter')handleClick()">
Save
</div>
<!-- Prefer: Native button -->
<button type="button" onclick="handleClick()">Save</button>
The native <button> gives you keyboard activation (Enter and Space), focus management, form submission support, and proper accessibility-tree mapping — with zero extra code.
Dialogs and Modals
<!-- Avoid: ARIA dialog on a div -->
<div role="dialog" aria-modal="true" aria-labelledby="title">
<h2 id="title">Confirm</h2>
<!-- requires custom JS for focus trapping, Escape key, etc. -->
</div>
<!-- Prefer: Native dialog element -->
<dialog id="confirm-dialog">
<h2>Confirm</h2>
<p>Are you sure?</p>
<button onclick="this.closest('dialog').close()">Cancel</button>
<button>OK</button>
</dialog>
The <dialog> element provides focus trapping, Escape-to-close, and proper modal behavior automatically.
Disclosure Widgets (Expand/Collapse)
<!-- Avoid: Custom disclosure with ARIA -->
<button aria-expanded="false" aria-controls="panel">More info</button>
<div id="panel" hidden>Additional content here.</div>
<!-- requires JS to toggle aria-expanded and hidden -->
<!-- Prefer: Native details/summary -->
<details>
<summary>More info</summary>
<p>Additional content here.</p>
</details>
Live Regions and Status Messages
<!-- Avoid: ARIA live region on a div -->
<div role="status" aria-live="polite">Calculation complete: 42</div>
<!-- Prefer: Native output element -->
<output>Calculation complete: 42</output>
The <output> element has an implicit role="status" and is announced by screen readers automatically.
Landmark Regions
<!-- Avoid: ARIA landmarks on divs -->
<div role="navigation">...</div>
<div role="main">...</div>
<div role="banner">...</div>
<div role="contentinfo">...</div>
<!-- Prefer: Native landmark elements -->
<nav>...</nav>
<main>...</main>
<header>...</header>
<footer>...</footer>
Form Validation
<!-- Avoid: ARIA for form states -->
<input type="text" aria-required="true" aria-disabled="false">
<!-- Prefer: Native HTML attributes -->
<input type="text" required>
HTML5 form attributes like required, disabled, pattern, min, max, and type (email, url, tel, date, etc.) provide built-in validation, native error messages, and correct mobile keyboard layouts.
Range Inputs
<!-- Avoid: Custom slider with ARIA -->
<div role="slider" tabindex="0"
aria-valuemin="0" aria-valuemax="100" aria-valuenow="50">
</div>
<!-- requires extensive JS for drag, keyboard arrows, value display -->
<!-- Prefer: Native range input -->
<input type="range" min="0" max="100" value="50">
Hiding Content
<!-- Avoid: ARIA hidden on a container -->
<div aria-hidden="true">
<!-- still possible to Tab into children -->
</div>
<!-- Prefer: inert attribute -->
<div inert>
<!-- truly non-interactive: can't be tabbed, clicked, or read -->
</div>
The inert attribute prevents all interaction — keyboard, mouse, and assistive technology — in a single declaration.
Complete Reference Table
| Purpose | Use Instead of ARIA | Native HTML |
|---|---|---|
| Clickable actions | <div role="button"> |
<button> or <input type="button"> |
| Modal dialogs | <div role="dialog"> |
<dialog> |
| Expand/collapse | aria-expanded + JS |
<details> and <summary> |
| Status messages | <div aria-live="polite"> |
<output> |
| Navigation | <div role="navigation"> |
<nav> |
| Main content | <div role="main"> |
<main> |
| Page header | <div role="banner"> |
<header> |
| Page footer | <div role="contentinfo"> |
<footer> |
| Sidebar | <div role="complementary"> |
<aside> |
| Required fields | aria-required="true" |
required attribute |
| Disabled controls | aria-disabled="true" |
disabled attribute |
| Input validation | aria-invalid + JS |
pattern, type, min, max |
| Sliders | <div role="slider"> |
<input type="range"> |
| Hidden content | aria-hidden="true" |
inert attribute |
| Date input | <div role="group"> + JS |
<input type="date"> |
Why Native Elements Are Better
Built-in Keyboard Behavior
Native elements come with keyboard support that you’d otherwise have to code yourself. A <button> responds to Enter and Space. A <select> supports arrow-key navigation. A <details> toggles with Enter. With ARIA on a <div>, you’re responsible for re-implementing all of this — and getting it right across browsers and devices.
Cross-Device Compatibility
Native elements adapt to the device automatically. An <input type="date"> shows a date picker on mobile and a dropdown on desktop. A <select> renders as a native picker on iOS and Android. ARIA-enhanced custom widgets don’t get this behavior — they display the same way everywhere, often with poor mobile usability.
Screen Reader Reliability
Screen readers on iOS (VoiceOver) and Android (TalkBack) sometimes ignore or misinterpret ARIA hints, but they consistently recognize native element roles. Using native HTML means your interface works reliably across all screen readers, not just desktop ones.
Smaller Code, Better Performance
ARIA-heavy markup means more attributes for the browser to process and more JavaScript for keyboard handling, focus management, and state updates. Native elements eliminate this overhead. Less code means faster parsing, rendering, and smaller HTML payloads.
Resilience to Specification Changes
The ARIA specification evolves over time. Roles or attributes you rely on today could be deprecated or redefined. Native HTML elements, by contrast, have decades of backwards compatibility. Using them insulates your code from ARIA specification churn.
Automatic Internationalization
Native form controls like <input type="date"> automatically respect the user’s locale settings for date formatting, number separators, and language. Custom ARIA widgets need manual internationalization work.
Reduced Maintenance
Every ARIA attribute you add is a potential point of failure. If aria-expanded gets out of sync with the actual UI state, screen reader users get incorrect information. Native elements keep their state in sync automatically because the browser manages it.
When ARIA Is Still Justified
ARIA remains valuable in specific situations:
- Complex widgets without HTML equivalents: Tree views, multi-select comboboxes, data grids, and tab panels don’t have native HTML counterparts. ARIA is the right tool here.
- Temporary bridges: When a native element exists but browser support is insufficient for your audience, ARIA can bridge the gap until support catches up.
- Supplementary information: ARIA attributes like
aria-describedby,aria-label(for icon-only buttons), andaria-live(for dynamic notifications beyond<output>) add context that HTML alone can’t convey. - State communication: Attributes like
aria-expandedon a<button>that controls a non-<details>panel, oraria-currentfor navigation, communicate state to assistive technologies.
The key principle: start with semantic HTML, then layer ARIA only where native elements fall short.
How CodeFrog Helps
CodeFrog’s accessibility testing detects many common issues, but automated tests can’t tell you whether you’re using the best element for the job — only whether the current markup has detectable violations. A <div role="button"> with proper ARIA might pass automated checks, but a native <button> would be better in every way.
Use CodeFrog’s testing as your starting point:
- Run accessibility scans to catch detectable violations
- Review your markup and replace ARIA-heavy patterns with native elements where possible
- Test with a screen reader (VoiceOver, NVDA) to verify the experience
- Test with keyboard only to confirm all interactions work without a mouse
Going beyond automated test results to use semantic HTML proactively is what separates a truly accessible site from one that merely passes a checklist.
Related Resources
- Accessibility Best Practices - Comprehensive guide to accessibility and HTML validation
- Getting Started with Accessibility - Quick start guide
- Accessibility Testing Guide - Detailed testing instructions
- Why Accessibility Matters - The human impact of accessibility
- Benefits of Valid HTML and Accessibility - SEO and performance benefits
External Resources
- Semantic HTML Explained (SiteLint) - In-depth guide to semantic HTML elements and their benefits
- Native Browser Elements You Can Use Instead of ARIA (SiteLint) - Comprehensive reference for replacing ARIA with native HTML
- Using ARIA — First Rule of ARIA Use (W3C)
- MDN: HTML Elements Reference
- WCAG 2.1 Guidelines
Automated tests are a starting point, not the finish line. Semantic HTML is the foundation of true accessibility — use native elements first, and reach for ARIA only when HTML falls short.