Accessibility foundations
Prefer native semantics
Choose the HTML element whose built-in meaning and behavior match the interaction before adding ARIA.
Native HTML elements come with meaning and behavior already attached. A link navigates. A button performs an action. A heading structures the content. A label names a control. When you pick the element that matches what the user is about to do, most of the accessibility work is already done.
This is also the first rule of ARIA, written by the people who made ARIA: if a native element does the job, use it instead of adding a role.
What you get for free
Compare the two versions of the Register control. Here is the div after someone “made it accessible” by hand:
<div
class="btn"
tabindex="0"
role="button"
onclick="submitRegistration()"
onkeydown="if (event.key === 'Enter' || event.key === ' ') submitRegistration()"
>Register</div>
And here is the native version:
<button type="submit">Register</button>
The second one is shorter, and it does more. Besides focus, role, and Enter and Space activation, a submit button fires the form’s submit event, respects disabled, runs the browser’s built-in validation, and gets a visible border in Windows High Contrast mode. Every one of those is a bug you’d have to find and re-implement on the div. Notice that the hand-made version already has a subtle one: Space fires on keydown, while native buttons fire on keyup.
Links versus buttons
On the flawed page, “Add a guest” opens a modal but is written as a link:
<a href="#" onclick="openGuestModal()">Add a guest</a>
VoiceOver announces “Add a guest, link”. A screen reader user expects to go somewhere. They might open it in a new tab. It also puts # in the URL and scrolls to the top. Since it performs an action, it should be a button:
<button type="button" onclick="openGuestModal()">Add a guest</button>
Now the announcement is “Add a guest, button”, the URL stays clean, and the expectation matches the behavior.
Don’t fight the element
ARIA can add a missing semantic, but it should never contradict the element you put it on. This heading:
<h2 role="button">Schedule</h2>
is no longer a heading in the accessibility tree. Users navigating by headings skip straight past the schedule. If you need a clickable heading, put a real button inside the h2.
Delete code, gain behavior
My habit when I review a page: find every generic element with a click handler and ask which native element it is pretending to be. On our registration page there are three: the Register div, the “Add a guest” link, and a span styled to look like the “I accept the terms” checkbox. Replacing them with button, button and input type="checkbox" removes about forty lines of keyboard and state handling. Fewer lines, fewer places to break.
Try this on your own project: replace three generic interactive elements with their native equivalent and count the lines you were able to delete.
Lesson completed