Accessible websites start with semantic HTML. When the HTML is meaningful, assistive technologies (like screen readers), browsers, and other tools can correctly understand and navigate your content. ARIA (Accessible Rich Internet Applications) is a set of attributes you can add when native HTML is not enough—however, ARIA should be used carefully and sparingly.
1) Start with semantic HTML (the best accessibility tool)
Semantic elements describe what something is, not just how it looks.
Common semantic elements and why they matter
<header>: introductory content for a page/section<nav>: major navigation links<main>: the primary content of the page (use once per page)<section>: a thematic grouping (usually with a heading)<article>: a self-contained piece of content (e.g. blog post, news item)<aside>: complementary content (e.g. related links)<footer>: footer content for a page/section<button>: a clickable control that performs an action<a>: a link that navigates to a new location (URL / anchor)- Headings (
<h1>…<h6>): content structure and navigation landmarks
Example: good page landmarks
<header>
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav aria-label="Primary">
<a href="/home">Home</a>
<a href="/services">Services</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main id="main-content">
<h1>Services</h1>
<section>
<h2>Website Development</h2>
<p>We build responsive, accessible websites.</p>
</section>
</main>
<footer>
<p>© 2026 Example Company</p>
</footer>
Why this helps: Screen reader users can jump directly to navigation, main content, and headings. The “skip link” helps keyboard users avoid tabbing through long menus on every page.
2) Headings: structure before styling
Headings are used for document outline, not for font sizes.
Good practice
- Use a single
<h1>for the page’s main heading. - Don’t skip levels for visual reasons (e.g. don’t jump from
<h2>to<h4>). - Use CSS to style headings instead of choosing a heading level just because it “looks right”.
<h1>About Us</h1>
<h2>Our mission</h2>
<h2>Our team</h2>
<h3>Leadership</h3>
<h3>Delivery</h3>
3) Forms: labels, instructions, and errors
Forms are one of the biggest accessibility pain points. The basics make a massive difference.
Always use <label> with form controls
Correct: label is programmatically linked to the input.
<label for="email">Email address</label>
<input id="email" name="email" type="email" autocomplete="email" required>
Avoid: placeholder-only labels. Placeholders disappear and are not reliable labels.
<!-- Not recommended -->
<input type="email" placeholder="Email address">
Group related inputs with fieldset and legend
Useful for radio buttons, checkboxes, and sections of a form.
<fieldset>
<legend>Preferred contact method</legend>
<label>
<input type="radio" name="contact" value="email">
Email
</label>
<label>
<input type="radio" name="contact" value="phone">
Phone
</label>
</fieldset>
Help text: connect it with aria-describedby
When extra instructions exist, link them so assistive tech reads them with the control.
<label for="password">Password</label>
<input id="password" name="password" type="password" aria-describedby="password-help">
<p id="password-help">Use at least 10 characters, including a number.</p>
Error messages: make them clear and connected
A simple pattern:
- Put an error message next to the field.
- Link it via
aria-describedby. - Use
aria-invalid="true"when invalid. - If errors appear after submitting, move focus to the error summary (covered later in the course).
<label for="id-number">ID number</label>
<input
id="id-number"
name="idNumber"
inputmode="numeric"
aria-invalid="true"
aria-describedby="id-error"
>
<p id="id-error">Please enter a valid ID number.</p>
4) Buttons vs links: choose the right element
This is a common real-world mistake.
- Use
<a>for navigation (go to another page/URL). - Use
<button>for actions (submit, open a modal, toggle a menu, like/unlike).
<!-- Navigation -->
<a href="/pricing">View pricing</a>
<!-- Action -->
<button type="button" id="open-modal">Open sign-up form</button>
Why it matters: Keyboard and assistive technologies treat links and buttons differently. Using the correct element gives you built-in keyboard support and expected behaviour.
5) Images: meaningful alt text (or no alt)
alt text should communicate the purpose of the image.
Decorative image
If the image adds no meaning, use empty alt:
<img src="divider.png" alt="">
Informative image
Describe what a user needs to know:
<img src="team.jpg" alt="The Cape Town delivery team standing outside the office">
Linked image
The alt should describe the link destination:
<a href="/home">
<img src="logo.svg" alt="Example Company home">
</a>
6) Landmarks and names: make navigation easier
Semantic elements create “landmarks”. Sometimes you have multiple landmarks of the same type (e.g. multiple <nav> areas). Give them accessible names.
<nav aria-label="Primary navigation">
<!-- main site links -->
</nav>
<nav aria-label="Footer navigation">
<!-- footer links -->
</nav>
7) ARIA basics: what it is (and what it isn’t)
ARIA adds extra meaning to HTML when native elements cannot express it. It’s helpful for custom UI components (like custom dropdowns, tabs, or modals).
Key idea: ARIA does not fix bad HTML
ARIA can’t:
- make a
<div>behave like a real button in all situations automatically - add keyboard support for you
- solve poor focus management
- correct confusing content or missing headings
8) The “first rule of ARIA”: don’t use ARIA if HTML already works
Prefer native elements because they come with:
- built-in keyboard interaction
- correct roles and states
- better browser and assistive tech support
Good: native button
<button type="button">Save</button>
Avoid: div pretending to be a button
<!-- Not recommended -->
<div role="button" tabindex="0">Save</div>
If you must use a custom element, you must also implement keyboard support (Enter/Space), focus styles, and state updates. In most cases, using <button> is simpler and more reliable.
9) Roles, states, and properties (practical overview)
ARIA attributes fall into a few useful categories:
Roles (what it is)
role="navigation",role="dialog",role="tablist"etc.
Note: many roles are already provided by semantic HTML. For example, <nav> implies navigation.
States/properties (what it’s like right now)
aria-expanded="true|false"for togglesaria-pressed="true|false"for toggle buttonsaria-selected="true|false"for selectable itemsaria-current="page"for current nav item
10) Useful ARIA patterns you will use often
A) Toggling a mobile menu: aria-expanded
<button
type="button"
aria-controls="primary-menu"
aria-expanded="false"
id="menu-button"
>
Menu
</button>
<nav id="primary-menu" hidden>
<a href="/home">Home</a>
<a href="/about">About</a>
</nav>
Behaviour to implement with JavaScript
- When the menu opens:
- remove
hiddenfrom the nav - set
aria-expanded="true"on the button
- remove
- When the menu closes:
- add
hiddenback - set
aria-expanded="false"
- add
This gives assistive tech a clear “open/closed” state.
B) Current page indicator: aria-current
<nav aria-label="Primary">
<a href="/home">Home</a>
<a href="/services" aria-current="page">Services</a>
<a href="/contact">Contact</a>
</nav>
This is more accessible than only relying on colour or styling to show the current page.
C) Icon-only buttons: use aria-label
If there’s no visible text, the button still needs a name.
<button type="button" aria-label="Search">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">
<!-- icon -->
</svg>
</button>
Notes:
aria-hidden="true"hides the decorative icon from screen readers.- The button’s accessible name becomes “Search”.
11) Avoid common ARIA mistakes
Don’t add roles that are already implied
<!-- Unnecessary -->
<nav role="navigation">
Don’t misuse aria-hidden
aria-hidden="true" removes content from screen readers, but the content may still be focusable with a keyboard if you’re not careful.
Bad example:
<div aria-hidden="true">
<a href="/secret">Hidden link</a>
</div>
If it can receive focus, it’s a problem. Prefer using hidden, display: none, or otherwise ensure it’s not focusable.
Don’t remove focus outlines without replacement
In CSS, avoid:
:focus { outline: none; }
If you restyle focus, make it clearly visible.
12) Quick accessibility checklist for your HTML
Use this when building pages and components:
- [ ] Page has a clear
<h1>and logical heading order. - [ ] Layout uses landmarks (
header,nav,main,footer) appropriately. - [ ] Links describe destination (avoid “Click here”).
- [ ] Buttons are used for actions, links for navigation.
- [ ] Every form control has a label (not placeholder-only).
- [ ] Errors and help text are connected (
aria-describedby), and invalid fields are marked (aria-invalid). - [ ] Images have appropriate
alt(oralt=""if decorative). - [ ] ARIA is used only when needed and states (like
aria-expanded) are kept in sync.
Practice activity
- Create a simple page with a header, nav, main content, and footer using semantic elements.
- Add a contact form with:
- linked labels (
for/id) - one help text connected with
aria-describedby
- linked labels (
- Add a mobile “Menu” button that toggles navigation and updates
aria-expanded.
You should be able to navigate the full page using only the Tab, Shift+Tab, Enter, and Space keys, with a clear focus indicator at all times.