Screen Reader Tricks Every Frontend Dev Should Know
Most accessibility bugs aren’t about missing alt text — they’re about assumptions baked into how we build things. Sighted testing alone won’t catch them, because a screen reader doesn’t experience your page the way your eyes do. Here are a few practical patterns that make a real difference, with the “why” behind each one.
1. The .sr-only (visually-hidden) class
Sometimes sighted users get enough context from layout and icons alone, but a screen reader user needs a bit more text to understand the same thing. The fix isn’t display: none — that removes content from the accessibility tree entirely, so a screen reader won’t see it either. Instead, use a class that hides content visually while keeping it in the tree:
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
Use it to add context sighted users get for free:
<a href="/plans">
Learn more<span class="sr-only"> about our mobile plans</span>
</a>
Without the hidden span, a page full of “Learn more” links is meaningless out of context when a screen reader user tabs through them one at a time.
2. aria-hidden, and its one big trap
aria-hidden="true" tells assistive tech to skip an element and everything inside it. It’s the right call for purely decorative stuff — icon fonts, SVG flourishes, a checkmark next to text that already says “Completed”:
<button>
<svg aria-hidden="true">...</svg>
Add to cart
</button>
The trap: aria-hidden does not remove an element from the tab order. If you hide a focusable element — a button or link — with aria-hidden, keyboard users can still tab to it, but their screen reader will announce nothing. That’s a silent, confusing dead stop. If something needs to be hidden from screen readers and keyboard navigation, pair it with tabindex="-1", or better, just remove it from the DOM.
3. Live regions for dynamic content
If your UI updates without a page reload — a form validation error, a “3 items in cart” counter, a toast notification — a screen reader has no way to know unless you tell it. aria-live announces changes automatically:
<div aria-live="polite" class="sr-only" id="cart-status"></div>
document.getElementById('cart-status').textContent = 'Item added to cart';
Use polite for most updates — it waits for a pause before announcing, so it doesn’t interrupt whatever the user is currently doing. Reserve assertive for things that genuinely can’t wait, like a session-timeout warning; it interrupts immediately, which gets annoying fast if overused.
4. Skip links
If your site has a nav bar with fifteen links, a keyboard or screen reader user has to tab through all of them, every single page, before reaching the actual content. A skip link solves this in two lines:
<a href="#main-content" class="skip-link">Skip to main content</a>
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px 16px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
It stays visually hidden until it’s focused (via Tab), then pops into view. This is a case where you want the element to be both visible and reachable, not permanently hidden.
5. Focus management for custom components
Custom modals, dropdowns, and tabs are where accessibility usually breaks, because the browser doesn’t automatically manage focus for you the way it does for native <dialog> or <select> elements. Two rules of thumb:
- When a modal opens, move focus into it (usually to the heading or the first interactive element), and trap Tab within it while it’s open.
- When it closes, return focus to whatever triggered it — don’t leave focus lost on
<body>, which forces a screen reader user to re-navigate from scratch.
function openModal(modal, trigger) {
modal.showModal();
modal.querySelector('h2')?.focus();
modal.dataset.trigger = trigger.id;
}
function closeModal(modal) {
modal.close();
document.getElementById(modal.dataset.trigger)?.focus();
}
6. Test with an actual screen reader
None of this substitutes for listening to your own site. VoiceOver ships free on macOS/iOS (Cmd+F5), NVDA is free on Windows, and both take about ten minutes to learn the basics of. Turn one on, close your eyes, and try to complete your site’s main user flow using only Tab, arrow keys, and Enter. It’s genuinely humbling the first time, and it surfaces problems no linter will ever catch.
