How Screen Readers Work with EchoQuest: A Technical Deep Dive
"Screen reader compatible" is one of the most abused phrases in accessibility. It often means "we added alt text to the images and tested it once in VoiceOver" — and the gap between that and a screen reader user actually being able to use the application is enormous. A blind player who has been turned away from many "accessible" apps over the years can usually tell within thirty seconds whether a site was designed with screen reader users in mind or whether the developers added accessibility as a checkbox at the end. EchoQuest takes a different approach — one built into the architecture rather than bolted on afterwards.
This post is a technical deep dive into how that's done. It's aimed at developers, accessibility advocates, and curious players who want to know what's happening under the hood. If you build web apps, the patterns below are worth applying to your own work; the underlying lesson is that screen reader compatibility is a design constraint, not a feature you ship later. Treat it as a constraint and most of the work is structural; treat it as a feature and you'll be patching forever.
How Screen Readers Work
A screen reader is software that reads the contents of your screen aloud and provides keyboard navigation. Common screen readers include NVDA and JAWS on Windows, VoiceOver on Mac and iOS, TalkBack on Android, and Orca on Linux. Different users prefer different combinations; we test against several to ensure the experience holds up regardless of the reader.
Screen readers work by reading the browser's accessibility tree — a structured representation of the page that the browser builds from your HTML and ARIA attributes. The accessibility tree is what the screen reader actually sees. The visual layout, the colours, the icons, the animations — none of that matters to a screen reader. Only the tree matters. When HTML is semantic and well-structured, the accessibility tree is accurate and useful. When it's not — when content is rendered via CSS, positioned absolutely, or conveyed through visual properties alone — the accessibility tree is incomplete or misleading. A button that looks like a button but is actually a styled div will appear in the tree as something other than a button, and the screen reader user will not be able to operate it the way they'd expect.
The single highest-leverage thing any web developer can do for accessibility is use the right HTML element for the right purpose. Almost every other accessibility technique is downstream of that choice.
EchoQuest's Approach: Semantic HTML First
Every interactive element in EchoQuest is a real HTML element with the semantics that match its purpose:
- Buttons are real button elements, not clickable divs. They get keyboard focus by default, are activated by Enter or Space without extra code, and announce as "button" to screen readers
- Navigation is in a nav element with an aria-label so users can jump to it via screen reader navigation shortcuts
- The game text is in main with an id so the skip link can jump to it from the top of the page
- Narration entries are p elements inside a live region so new content is announced automatically as it arrives
- Form inputs always have associated label elements; placeholder text is never the only label
- Headings follow proper hierarchy — h1 for page title, h2 for major sections, h3 for subsections — so screen reader heading-navigation works
- Lists use ul, ol, and li elements rather than styled divs, so screen reader users can hear "list with 5 items"
- Tables use table, thead, tbody, tr, th, and td appropriately, with caption elements where they add useful context
This sounds basic, but the majority of web accessibility failures come from ignoring exactly these basics. Modern frontend frameworks make it easy to ship custom components that look right but lose the underlying semantics; we audit constantly to catch ourselves before that happens.
ARIA Live Regions for Dynamic Content
The most important accessibility feature in EchoQuest is its use of ARIA live regions for game content.
When the AI GM generates a response, it appears dynamically — the page doesn't reload. Without live regions, a screen reader user would never hear the new content unless they navigated to it manually, which would defeat the purpose of an interactive narrative. With live regions, the new narration is automatically announced as soon as it appears, so the screen reader user gets the same real-time response a sighted player gets visually.
EchoQuest uses role="status" for non-urgent announcements (choice updates, inventory changes, faction reputation shifts) and role="alert" for urgent ones (HP reaching zero, critical story moments, narrative cliffhangers that demand attention). The distinction matters: "status" announcements wait for a natural pause in the screen reader's current speech; "alert" announcements interrupt immediately. Using "alert" too often is a common mistake — it makes the experience feel jittery and aggressive. We use it sparingly, only for moments where interrupting the current narration genuinely serves the player.
A subtler choice: how often live regions update. If we naively pushed every token of streamed AI output into a live region, the screen reader would get hammered. Instead, the live region updates at sentence boundaries, which produces much cleaner narration without sacrificing real-time feel.
Focus Management
When a modal opens (settings, character sheet, inventory), focus moves automatically to the first interactive element inside it. When the modal closes, focus returns to the element that triggered it. This is the standard web accessibility pattern — but it requires explicit JavaScript to implement and is frequently missed in production apps. Every page on the public internet that has a modal you can't escape from with the keyboard, or whose focus jumps to the top of the page when you close a dialog, is failing to implement this correctly.
EchoQuest's FocusManager component handles this centrally, ensuring every modal and overlay follows the pattern consistently. We also trap focus inside modal dialogs so Tab cycling stays within the dialog — preventing the common bug where keyboard users tab themselves out of a modal into the page behind it without realising the modal is still open.
We also explicitly manage focus around route changes in Next.js. When the player navigates to a new page (say, from the library to a character creation flow), focus moves to the new page's main heading, so screen reader users immediately hear where they've landed. Without this, focus would stay where the click happened, and the screen reader user might not realise the page has changed at all.
Skip Links and Heading Navigation
Every page has a "Skip to main content" link as the very first focusable element. It's visually hidden until focused, then becomes visible at the top-left of the screen. For repeat visitors who navigate by keyboard or screen reader, the skip link is the single most useful element on the page — it lets you bypass the navigation header without tabbing through it on every page load.
We also structure pages with proper heading hierarchy so screen reader users can navigate by heading level. NVDA's "h" key, JAWS's "h" key, and VoiceOver's heading rotor all let users jump from heading to heading. A well-structured page becomes navigable at a level that's faster than visual scrolling.
Testing Process
We test with:
- NVDA + Firefox on Windows (the most common screen reader/browser combination used by blind Windows users)
- JAWS + Chrome on Windows (the most common combination in enterprise and education)
- VoiceOver + Safari on Mac and iOS
- TalkBack + Chrome on Android (smoke test on each release)
- Orca + Firefox on Linux (smoke test)
- Keyboard-only navigation (no screen reader, just Tab/Enter/arrow keys) on every browser
Accessibility testing is part of our CI pipeline via axe-core automated checks, which catch a useful subset of issues before they reach production. But automated testing alone misses the most important class of bugs — the ones where semantically valid markup produces a confusing experience. Those bugs only show up under manual testing with a screen reader, and we do that with each significant feature change. We also work with blind testers on a freelance basis for major releases. Their feedback consistently surfaces issues no internal team would have caught.
Reporting Issues
If you encounter an accessibility barrier in EchoQuest, please report it via our support link. We treat accessibility bugs as P1 issues — they block releases. The fastest channel is email, with the browser, screen reader version, and exact steps to reproduce. We respond to every report and fix as quickly as we can. We'd rather know about a bug a hundred users have hit silently than miss it because nobody told us.