Making Content Perceivable
Remember the first POUR principle? Content must be Perceivable—users must be able to perceive the information you're presenting. This starts with providing text alternatives for non-text content and using semantic HTML so assistive technologies can understand your content's structure.
Text Alternatives for Images and Non-text Content
All meaningful non-text content (images, icons, SVGs, input image buttons, etc.) should have an equivalent text alternative so that users who cannot see them can still understand their purpose.
The alt Attribute: Your New Best Friend
For images, use the alt attribute with a concise description:
<img src="chart.png" alt="Bar chart showing 25% increase in sales from 2023 to 2024">
Writing good alt text:
- Be descriptive but concise: Describe what the image shows and why it matters in context
- Skip "image of" or "picture of": Screen readers already announce it's an image
- Consider context: The same image might need different alt text depending on where it appears
When to Use Empty Alt Text
If an image is purely decorative (doesn't convey information not already in text), use an empty alt attribute:
<img src="decorative-flourish.png" alt="">
This tells screen readers to ignore the image. Don't skip the alt attribute entirely—an empty string is intentional; no attribute makes the screen reader announce the filename, which is usually unhelpful.
Complex Images Need More
For complex images like infographics, charts, or diagrams, a short alt isn't enough. You need to provide a longer description:
- Include a text description in the surrounding content
- Link to a page with a detailed description
- Use the
longdescattribute (though it's not widely supported) - Provide the data in an accessible table format
Example:
<img src="sales-chart.png" alt="Sales by quarter - see data table below">
<table>
<caption>Sales by Quarter 2024</caption>
<!-- table data here -->
</table>
Form Controls and Buttons
Form inputs need labels, and buttons need text that describes what they do:
<!-- Good: visible label -->
<label for="email">Email Address:</label>
<input type="email" id="email" name="email">
<!-- Good: button with text -->
<button type="submit">Send Message</button>
<!-- Icon-only button needs aria-label -->
<button aria-label="Delete item">
<span class="icon-trash"></span>
</button>
Audio and Video: Captions and Transcripts
Multimedia content needs text equivalents for users who can't hear or see it.
Video Captions (Required)
Videos with audio must have captions synchronized with the dialogue and relevant sounds. This satisfies WCAG 1.2.2 (Level A).
Captions benefit:
- Deaf or hard-of-hearing users (obviously)
- Anyone in a noisy environment (like a busy coffee shop)
- Anyone in a quiet environment (like a library where they can't play sound)
- Non-native speakers learning the language
Implementation:
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="en" label="English">
</video>
Audio Transcripts
For audio-only content (podcasts, audio clips), provide a text transcript so users who can't hear can read the content.
Audio Descriptions for Video
If your video has important visual information that isn't conveyed in the audio (like text on screen, actions without dialogue, or visual demonstrations), you need audio description—narration of those visual details.
This is Level AA for prerecorded videos (WCAG 1.2.5). In practice, you can either include the descriptions in the main audio track or provide a separate described version.
Using Semantic HTML Structure
This is where a lot of accessibility wins happen with minimal effort. Using the right HTML elements for their intended purpose gives assistive technologies the information they need to understand and navigate your content.
Headings: The Navigation Highway
Use <h1> through <h6> tags for headings—not just bold text with larger font.
Why it matters: Screen reader users often navigate by jumping through headings. A logical heading hierarchy is like a table of contents for your page.
Best practices:
- One
<h1>per page for the main title <h2>for major sections<h3>for subsections under h2, and so on- Don't skip levels (don't jump from h2 to h4)
- Don't choose heading levels based on visual size—use CSS for that
Example:
<h1>Guide to Semantic HTML</h1>
<h2>Why Semantic HTML Matters</h2>
<p>Content...</p>
<h3>Screen Reader Benefits</h3>
<p>Content...</p>
<h3>SEO Benefits</h3>
<p>Content...</p>
<h2>Common Semantic Elements</h2>
<p>Content...</p>
Lists: Not Just for Bullets
Use <ul> for unordered lists and <ol> for ordered lists—don't just add bullet characters to divs.
<!-- Good -->
<ul>
<li>First item</li>
<li>Second item</li>
</ul>
<!-- Bad -->
<div>• First item</div>
<div>• Second item</div>
Screen readers announce how many items are in a proper list, which gives users context. With divs, they have no idea.
Landmarks: Signposts for Your Page
HTML5 semantic elements create landmarks that help users navigate to major sections:
<header>– Site or page header<nav>– Navigation menus<main>– Main content (use only once per page)<aside>– Sidebar or tangentially related content<footer>– Site or page footer<article>– Self-contained content (like blog posts)<section>– Thematic grouping of content
Basic page structure:
<header>
<h1>Site Name</h1>
<nav>
<!-- navigation links -->
</nav>
</header>
<main>
<article>
<h2>Article Title</h2>
<!-- article content -->
</article>
</main>
<footer>
<!-- footer content -->
</footer>
Many screen readers let users jump directly to landmarks ("Go to main content," "Go to navigation"), making these semantic elements incredibly useful for navigation.
Tables: Data, Not Layout
Use tables for tabular data, not for page layout. And when you do use tables, structure them properly:
<table>
<caption>Sales by Region</caption>
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">North</th>
<td>$150K</td>
<td>$175K</td>
</tr>
<!-- more rows -->
</tbody>
</table>
The <caption> describes the table. The <th> elements with scope attributes help screen readers associate data cells with their headers.
When and How to Use ARIA
ARIA (Accessible Rich Internet Applications) lets you add extra semantic information to HTML. But here's the golden rule: No ARIA is better than bad ARIA.
The First Rule of ARIA
If you can use a native HTML element or attribute, do that instead of adding ARIA. For example:
<!-- Good: native button -->
<button>Click me</button>
<!-- Bad: div pretending to be button -->
<div role="button" tabindex="0" onclick="...">Click me</div>
The native <button> comes with keyboard support, focus management, and proper semantics built-in. The div needs all of that added manually.
When ARIA is Actually Useful
ARIA is valuable for:
1. Providing Accessible Names
When visible text isn't enough or doesn't exist:
<!-- Icon-only button -->
<button aria-label="Close dialog">
<span class="icon-x"></span>
</button>
<!-- Using existing text as label -->
<div role="region" aria-labelledby="section-heading">
<h3 id="section-heading">Latest News</h3>
<!-- content -->
</div>
2. Indicating State
For interactive components that change state:
<!-- Accordion section -->
<button aria-expanded="false" aria-controls="content-1">
Section 1
</button>
<div id="content-1" hidden>
<!-- content -->
</div>
<!-- When opened, aria-expanded becomes "true" and hidden is removed -->
3. Creating Custom Widgets
When building custom interactive components that don't have native HTML equivalents:
<!-- Tabs interface -->
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
Tab 1
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
Tab 2
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<!-- panel 1 content -->
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
<!-- panel 2 content -->
</div>
4. Live Regions for Dynamic Content
When content updates without a page refresh, announce it to screen readers:
<!-- Status message -->
<div role="status" aria-live="polite">
Items added to cart: 3
</div>
<!-- Error/alert (interrupts immediately) -->
<div role="alert">
Form submission failed. Please check your entries.
</div>
ARIA Dos and Don'ts
Do:
- Use native HTML whenever possible
- Test with screen readers to verify it works as expected
- Follow established ARIA patterns (check WAI-ARIA Authoring Practices)
- Keep ARIA attributes up to date when state changes
Don't:
- Add ARIA roles that conflict with native element roles
- Use ARIA just for styling hooks (use classes instead)
- Forget to manage keyboard interaction (ARIA only handles semantics, not behavior)
- Hide content with
aria-hidden="true"that keyboard users need to access
Forms: The Accessibility Gauntlet
Forms are where many sites fail accessibility. But they're also straightforward to fix.
Every Input Needs a Label
Every form control needs a visible label properly associated with it:
<!-- Method 1: for/id association -->
<label for="username">Username:</label>
<input type="text" id="username" name="username">
<!-- Method 2: wrapping -->
<label>
Email:
<input type="email" name="email">
</label>
Placeholders are NOT labels. They disappear when you start typing, and screen readers don't always announce them properly.
Group Related Fields
For groups of checkboxes or radio buttons, use <fieldset> and <legend>:
<fieldset>
<legend>Choose your t-shirt size:</legend>
<label><input type="radio" name="size" value="s"> Small</label>
<label><input type="radio" name="size" value="m"> Medium</label>
<label><input type="radio" name="size" value="l"> Large</label>
</fieldset>
Indicate Required Fields
Use the required attribute and provide a visual indicator:
<label for="email">Email: <span aria-label="required">*</span></label>
<input type="email" id="email" name="email" required>
Make sure to explain what the asterisk means (add a note at the top of the form like "* indicates required field").
Key Takeaways
- All meaningful images need descriptive alt text; decorative images need empty alt (
alt=""). - Videos need captions; audio needs transcripts.
- Use semantic HTML elements (
<header>,<nav>,<main>,<h1>-<h6>, etc.) for their intended purpose. - Landmarks and heading structure help users navigate your page.
- Every form input needs a proper label—placeholders don't count.
- Use native HTML before reaching for ARIA.
- When you do use ARIA, follow established patterns and test with screen readers.
With solid semantic HTML in place, the next step is making sure users can actually interact with your content using a keyboard. Let's tackle keyboard navigation.