Skip to main content
Skip to docs content

Storybook Screen Reader Addon

The Speakable Storybook addon predicts screen reader output for every component story in real time. See what NVDA, JAWS, VoiceOver, and Narrator would announce without leaving your component development workflow. Catch accessibility issues while you build, not after you ship.

Speakable Storybook addon showing predicted NVDA screen reader output for a form component with checkboxes, radio buttons, and text inputs
The Speakable addon panel in Storybook, showing predicted NVDA output for a form with mixed states.

What the Addon Shows You

The addon adds a "Screen Readers" panel to Storybook. When you render any story, it instantly shows predicted speech output for four screen readers, plus an accessibility audit.

Per-Reader Predictions

Separate tabs for NVDA, JAWS, VoiceOver, and Narrator. Each produces genuinely different output reflecting how that reader handles roles, states, and announcement order.

Live Updates on Interaction

Change a control or toggle a state in your story. The predicted output updates immediately to reflect the new ARIA states.

Interaction Timeline and Diff

Declare an interaction sequence and the Timeline tab records focus moves, ARIA state changes, and announcements. The Diff tab flags regressions against a saved baseline.

Accessibility Audit

The Audit tab flags missing accessible names, heading hierarchy violations, and unnamed landmarks with severity levels and suggestions.

Element Statistics

A stats bar shows total elements, interactive elements, landmarks, and headings for quick structural awareness.

How Screen Reader Output Differs Per Reader

The addon uses the same Speakable engine that powers the CLI and MCP server. Each reader produces genuinely differentiated output. Here are some common differences you will see across tabs:

ElementNVDAJAWSVoiceOverNarrator
LinkHome, linkHome, clickableHome, linklink, Home
HeadingWelcome, heading level 1Welcome, heading 1heading level 1, WelcomeHeading level 1, Welcome
NavigationMain, navigation landmarkMain, navigation regionnavigation, Mainnavigation, Main
ImageLogo, graphicLogo, graphicLogo, imageLogo, image
Disabled buttonSave, button, unavailableSave, button, unavailableSave, button, dimmedSave, button, disabled
Checkbox (unchecked)Accept, checkbox, not checkedAccept, check box, not checkedAccept, checkbox, uncheckedAccept, check box, unchecked

How to Install the Storybook Addon

The addon ships as part of the @reticular/speakable package. No extra package to install. If you already use Speakable for CLI or MCP, you have the addon ready.

Step 1: Install Speakable

If you have not installed Speakable yet:

Terminal
npm install --save-dev @reticular/speakable

Step 2: Add to Storybook Config

Add the addon to your .storybook/main.ts (or .storybook/main.js):

.storybook/main.ts
import type { StorybookConfig } from '@storybook/your-framework';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|ts|tsx)'],
  addons: [
    '@storybook/addon-essentials',
    '@reticular/speakable/storybook', // Add this line
  ],
  framework: '@storybook/react-vite', // or your framework
};

export default config;

Step 3: Start Storybook

Start Storybook as usual. The "Screen Readers" panel appears at the bottom alongside your other addon panels (Actions, Controls, etc.):

Terminal
npm run storybook

No configuration required beyond adding it to your addons array. The addon automatically analyzes every rendered story.

How the Addon Works Under the Hood

1.

A Storybook decorator wraps every story. After each render, it captures the story's live DOM.

2.

The core Speakable engine builds an accessibility tree from the DOM: roles, names, states, values, and hierarchy.

3.

Four separate renderers produce output for NVDA, JAWS, VoiceOver, and Narrator, each with its own announcement order, role vocabulary, and state phrasing.

4.

A MutationObserver watches for DOM changes (attribute mutations on ARIA states, child additions/removals). When the component updates, predictions refresh automatically.

5.

If the story declares a parameters.speakable sequence, the decorator runs it, attaches the runtime engine, and records an accessibility event timeline. When a baseline exists, it diffs against it and populates the Timeline and Diff tabs.

6.

The audit engine runs concurrently, checking heading hierarchy, landmark structure, and interactive elements for missing accessible names.

Supported Frameworks

The addon works with any Storybook framework that renders to the DOM. The analysis runs on the final rendered HTML, so it is framework-agnostic:

@storybook/react-vite
@storybook/react-webpack5
@storybook/vue3-vite
@storybook/svelte-vite
@storybook/html-vite
@storybook/web-components-vite

Requires Storybook 8.x. The addon uses the Storybook Manager API and panel registration system introduced in Storybook 7, but is tested and optimized for version 8.

Using the Addon with Storybook Controls

Combine the addon with Storybook Controls to explore how prop changes affect screen reader output. For example, toggling a disabled prop will show the reader output update from "button" to "button, unavailable" (NVDA) or "button, dimmed" (VoiceOver) in real time.

Tip: Write stories that isolate specific ARIA patterns. A story for "Button expanded" and another for "Button collapsed" makes cross-reader differences immediately obvious without needing to interact.

Understanding the Audit Tab

The Audit tab runs a lightweight accessibility check on the rendered story. It reports issues that would affect screen reader users:

Error

Missing accessible name on interactive elements (buttons, links, inputs without labels)

Warning

Heading hierarchy violations (skipped levels), unnamed landmarks

Info

No landmarks found (consider adding semantic structure)

The audit runs the same checks as the CLI audit command and the MCP audit_html tool.

How to Capture an Interaction Timeline in Storybook

The per-reader tabs analyze the story at a point in time. The Timeline tab goes further: it runs an interaction sequence against the rendered story and records the accessibility events that result, in order. That includes focus transitions, ARIA state changes, live region announcements, and dialog open and close events. This is how you verify dynamic behavior, such as a modal moving focus on open and restoring it on close, directly in Storybook.

Timeline capture is opt-in per story. Add a parameters.speakable block with either a full sequence or a named built-in pattern. The sequence runs automatically once after the story renders, so the Timeline reflects that scripted run rather than manual clicking in the preview.

Option 1: Use a built-in interaction pattern

Built-in patterns cover common ARIA widgets. Override the selectors to match your markup:

Dialog.stories.ts
export const Dialog = {
  render: () => renderDialog(),
  parameters: {
    speakable: {
      // 'modal-dialog' | 'combobox' | 'tabs' | 'accordion'
      pattern: 'modal-dialog',
      selectors: { trigger: 'button.open', container: '[role="dialog"]' },
    },
  },
};

Option 2: Write a custom sequence

When a built-in pattern does not match your component, declare the actions yourself. Supported actions include tab, shiftTab, click (with a selector), enter, space, escape, the arrow keys, key (with a combo), and wait (with ms).

Disclosure.stories.ts
export const Disclosure = {
  render: () => renderDisclosure(),
  parameters: {
    speakable: {
      sequence: {
        description: 'Tab to the toggle and open it',
        actions: [{ type: 'tab' }, { type: 'enter' }],
      },
    },
  },
};

// Opt a story out of timeline capture (static tabs still work)
export const StaticOnly = {
  render: () => renderThing(),
  parameters: { speakable: { timeline: false } },
};

parameters.speakable reference

FieldTypeDescription
sequence{ description, actions[] }An explicit interaction sequence. Highest priority.
pattern'modal-dialog' | 'combobox' | 'tabs' | 'accordion'A named built-in interaction pattern.
selectors{ trigger?, container?, content?, items?, input? }Selector overrides for the chosen built-in pattern.
timelinefalseDisables timeline capture for the story.

Set a baseline and catch regressions with the Diff tab

On the Timeline tab, click Set baseline to store the current timeline for that story. Baselines persist in the browser, keyed per component and story. On later renders, the Diff tab compares the current timeline against the baseline and classifies each change by severity (critical, high, medium, low). A broken focus trap or a dropped announcement then surfaces as a regression instead of a silent behavior change.

For headless, whole-library regression runs in CI, use the CLI instead. See Runtime Analysis for the speakable runtime --storybook pipeline with snapshot baselines and CI mode.

Speakable Addon vs @storybook/addon-a11y

Both addons help with accessibility, but they solve different problems and work well together:

AspectSpeakable Addon@storybook/addon-a11y
FocusScreen reader output predictionWCAG rule violations (via axe-core)
OutputWhat users hear, per readerPass/fail rule results
Best forVerifying announcement quality and contentCatching WCAG compliance issues
Reader differencesYes (4 readers with distinct output)No (generic rule-based)
Use together?Yes. Use both for comprehensive coverage.

Limitations and Accuracy

The addon provides heuristic predictions, not a screen reader emulator. Keep these limitations in mind:

  • Predictions approximate common screen reader behavior. Real output varies by reader version, user settings, and verbosity level.
  • The per-reader tabs show the DOM state at a point in time. To capture focus management (focus trapping, focus restore) and live region announcements over time, add a parameters.speakable sequence and read the Timeline tab.
  • Timelines run the scripted sequence you declare, executed once after render. They do not record free-form manual interaction in the preview.
  • Live region timing and interruption behavior differs across readers. The Timeline records that an announcement occurred, not each reader's exact interruption model.
  • CSS-based hiding (display: none, visibility: hidden) is detected, but complex CSS state transitions may not be captured on initial render.

Always verify with real screen readers before release. The addon is a development-time tool for catching obvious issues and understanding cross-reader differences. It does not replace testing with NVDA, JAWS, VoiceOver, or Narrator on actual devices.

Related Pages