Difficulty: Intermediate
Screen readers are assistive technologies that convert on-screen content into synthesized speech or braille output. They are used by people who are blind, have low vision, or have cognitive disabilities. Major screen readers include JAWS and NVDA on Windows, VoiceOver on macOS and iOS, and TalkBack on Android. Understanding how screen readers interpret your HTML is fundamental to building truly accessible web experiences.
Screen reader only (sr-only) text is content that is visually hidden from sighted users but remains accessible to screen readers. This is used when visual context makes something obvious to sighted users but that context is not available through the accessibility tree. The standard CSS pattern for sr-only text uses a combination of position: absolute, width/height of 1px, overflow: hidden, and clip to make the element invisible while keeping it in the accessibility tree. Never use display: none or visibility: hidden for this purpose, as those properties remove content from the accessibility tree entirely.
Alt text for images is one of the most impactful accessibility practices. Every informative image must have descriptive alt text that conveys the same information the image provides visually. Decorative images should have an empty alt attribute (alt="") so screen readers skip them. Complex images like charts or infographics need either a lengthy alt text or a nearby text description linked via aria-describedby. Avoid generic alt text like "image" or "photo" - describe what the image actually shows and why it matters in context.
Live regions are essential for dynamic content that changes after the initial page load. When search results update, a toast notification appears, a chat message arrives, or a countdown timer ticks, screen readers will not automatically announce these changes unless the containing element has appropriate aria-live attributes. The two main values are polite (announced when the user finishes their current activity) and assertive (interrupts immediately). Use aria-atomic="true" when you want the entire region content re-announced on any change, and aria-relevant to specify what types of changes to announce (additions, removals, text, or all).
Testing with screen readers is irreplaceable. While automated tools can catch structural issues like missing alt text or invalid ARIA, they cannot evaluate whether the user experience is actually coherent and usable. A screen reader test involves navigating the page using only keyboard shortcuts: reading by headings (H key in NVDA/JAWS), landmarks (D key), links (K key), and form fields (F key). Listen to how your content is read aloud - does the heading structure make sense? Are form labels clear? Do dynamic updates get announced? Testing with at least one screen reader should be part of every accessibility review.
<style>
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
</style>
<!-- Icon button with sr-only label -->
<button>
<svg aria-hidden="true" viewBox="0 0 24 24"><!-- trash icon --></svg>
<span class="sr-only">Delete item</span>
</button>
<!-- Badge with sr-only context -->
<a href="/notifications">
<svg aria-hidden="true" viewBox="0 0 24 24"><!-- bell icon --></svg>
<span class="badge">5</span>
<span class="sr-only">5 unread notifications</span>
</a>
<!-- Table column with visual abbreviation -->
<th>
Qty
<span class="sr-only"> (Quantity)</span>
</th>
The sr-only class hides text visually while keeping it accessible to screen readers. Use it for icon buttons, badges that need context, and abbreviated table headers. Mark decorative icons with aria-hidden='true' so screen readers skip them.
<!-- Informative image: Describe what it conveys -->
<img
src="team-photo.jpg"
alt="The engineering team of 12 people standing in front of the office building, smiling"
/>
<!-- Functional image: Describe the action -->
<a href="/">
<img src="logo.svg" alt="Acme Corp - Go to homepage" />
</a>
<!-- Decorative image: Empty alt -->
<img src="decorative-wave.svg" alt="" />
<!-- Complex image: Link to longer description -->
<figure>
<img
src="sales-chart.png"
alt="Quarterly sales chart showing steady growth"
aria-describedby="chart-desc"
/>
<figcaption id="chart-desc">
Sales increased from $2.1M in Q1 to $3.8M in Q4 2025,
with the largest jump of 40% occurring between Q2 and Q3.
</figcaption>
</figure>
<!-- Image in a link: Alt describes the destination -->
<a href="/products/headphones">
<img src="headphones.jpg" alt="Wireless Noise-Cancelling Headphones - View product details" />
</a>
Alt text should describe the purpose and content of the image in context. Functional images describe the action, decorative images use empty alt, and complex images need extended descriptions. Never start alt text with 'image of' or 'picture of' as screen readers already announce the image role.
<!-- Screen readers navigate by headings, landmarks, and lists -->
<!-- Good: Logical heading hierarchy -->
<main>
<h1>Product Catalog</h1>
<section aria-labelledby="electronics-heading">
<h2 id="electronics-heading">Electronics</h2>
<article>
<h3>Wireless Headphones</h3>
<p>Premium sound quality with noise cancellation.</p>
<p><strong>$199.99</strong></p>
</article>
<article>
<h3>Bluetooth Speaker</h3>
<p>Portable speaker with 20-hour battery life.</p>
<p><strong>$89.99</strong></p>
</article>
</section>
<section aria-labelledby="books-heading">
<h2 id="books-heading">Books</h2>
<!-- More products -->
</section>
</main>
<!-- Bad: Headings used for styling, not structure -->
<div>
<h3>Product Catalog</h3> <!-- Should be h1 -->
<h1>Electronics</h1> <!-- Should be h2 -->
<h5>Wireless Headphones</h5> <!-- Should be h3 -->
</div>
Screen readers let users navigate by headings (H1-H6). Headings must follow a logical hierarchy without skipping levels. Sections should be labeled with aria-labelledby. This structure lets screen reader users quickly scan the page like a table of contents.
<!-- Search with live result announcements -->
<div role="search">
<label for="search">Search products</label>
<input
id="search"
type="search"
aria-describedby="search-count"
aria-autocomplete="list"
aria-controls="results-list"
/>
</div>
<!-- Polite announcement of result count -->
<div id="search-count" aria-live="polite" aria-atomic="true">
Showing 24 of 156 products
</div>
<ul id="results-list">
<li>Product 1</li>
<li>Product 2</li>
</ul>
<!-- Toast notification system -->
<div
id="toast-container"
aria-live="polite"
aria-relevant="additions"
style="position: fixed; bottom: 16px; right: 16px;"
>
<!-- Toasts are appended here by JavaScript -->
</div>
<script>
function showToast(message, type) {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.setAttribute('role', type === 'error' ? 'alert' : 'status');
toast.textContent = message;
container.appendChild(toast);
setTimeout(() => toast.remove(), 5000);
}
// Usage:
showToast('Item added to cart', 'success');
showToast('Failed to save changes', 'error');
</script>
Dynamic updates need live regions so screen readers announce changes. Use aria-atomic='true' when the whole region should be re-read on any change. Toast notifications should use role='status' for info/success and role='alert' for errors.
Screen Reader Only Text, Alt Text, Live Regions, Testing, Semantic HTML