Runtime Accessibility Analysis
Speakable now extends beyond static markup analysis to observe accessibility behavior during component interaction. Static analysis catches missing labels and broken ARIA attributes, but many critical accessibility defects only emerge at runtime: focus escaping modal dialogs, broken focus restoration after dialog close, missing live region announcements, and keyboard navigation regressions that appear only when users interact with widgets. Runtime analysis captures these behaviors as they happen, giving you visibility into what assistive technology users actually experience during interaction.
What is an Accessibility Timeline?
An Accessibility Timeline is a timestamped sequence of accessibility-relevant events captured during user interaction with a component. Think of it as a recording of everything that matters to assistive technology users: where focus moves, what gets announced, which states change, and when dialogs open or close.
Each timeline captures four categories of events:
- Focus transitions: where keyboard focus moves during interaction, including the role and accessible name of each focused element
- Announcements: text communicated to screen readers via aria-live regions, with politeness level (polite or assertive)
- State changes: ARIA state mutations like aria-expanded, aria-selected, and aria-checked toggling during interaction
- Dialog lifecycle: when dialogs open and close, whether they are modal, and whether focus management follows expected patterns
By capturing these events as a sequence, you can review exactly what happened during an interaction, compare timelines across builds to detect regressions, and identify patterns that indicate broken accessibility behavior.
How It Works
The Runtime Accessibility Engine follows a five-stage pipeline to generate timelines from component interactions:
Attach Engine
The Runtime Accessibility Engine attaches to the target document and installs observers for focus changes, DOM mutations, live region updates, and dialog state transitions.
Execute Interactions
An interaction sequence (clicks, keyboard events, tab navigation) is executed against the component. The engine waits for DOM stability between each action.
Collect Events
As interactions execute, the engine captures every accessibility-relevant event with a monotonically increasing timestamp relative to session start.
Generate Timeline
After interactions complete, a settle period allows trailing asynchronous events to be captured. The engine then produces a complete Accessibility Timeline with ordered events and session metadata.
Detect Warnings
The heuristic analyzer processes the event stream in real-time, emitting warnings for common accessibility anti-patterns like focus escaping dialogs or missing keyboard responses.
Built-in Patterns
Speakable includes built-in interaction patterns for common ARIA widgets. These patterns encode the expected keyboard behavior for each widget type, so you can generate timelines without writing custom interaction sequences for every component.
Modal Dialog
Opens a trigger, verifies focus moves into the dialog, tabs through dialog content, presses Escape to close, and verifies focus returns to the original trigger.
Combobox
Focuses the input, types to filter options, navigates with arrow keys, selects with Enter, and verifies the selection is announced to assistive technology.
Tabs
Focuses the tab list, navigates between tabs with arrow keys, activates with Enter or Space, and verifies panel content changes are communicated.
Accordion
Focuses the first header, toggles with Enter or Space, navigates between accordion headers, and verifies expanded/collapsed state announcements.
Heuristic Warnings
Even without a comparison baseline, the Runtime Accessibility Engine automatically detects common accessibility anti-patterns and emits warnings. These heuristics run in real-time during timeline capture, flagging issues as they occur.
Focus not moved to dialog
A modal dialog opened but focus was not moved to an element inside it within 100ms. Keyboard users will be stranded outside the dialog with no indication it appeared.
Focus escaped modal dialog
Focus moved to an element outside a modal dialog while the dialog is still open. This indicates a broken focus trap, allowing keyboard users to interact with content behind the modal.
Rapid announcements detected
More than 3 announcements from aria-live regions within 500ms. Assistive technology users may miss content when announcements overlap or queue up too quickly.
No keyboard response
A keyboard action was performed but no observable accessibility event occurred within 200ms. The component may not be responding to keyboard input, leaving keyboard-only users unable to interact.
Focused element removed
An element with focus was removed from the DOM without explicitly moving focus to another element. The browser will reset focus to the document body, losing the user's position in the page.
Interactive Demo
Compare working implementations against broken versions to see how accessibility regressions appear in the timeline. Toggle between variants to observe the difference in events and warnings.
Modal Dialog Pattern
Interact with the button below to see focus management in action
Usage Examples
The runtime engine can be used programmatically, via the CLI, or through Storybook integration. Below are practical examples for each approach.
Programmatic API
Import the runtime module to create timeline generators, execute interaction patterns, and inspect the resulting accessibility events directly in your code.
import { runtime } from '@reticular/speakable';
// Create a timeline generator for your component
const generator = runtime.createTimelineGenerator({
document: myDocument,
componentName: 'ConfirmDialog',
});
// Use a built-in interaction pattern
const sequence = runtime.getBuiltinPattern('modal-dialog', {
trigger: 'button.open-dialog',
});
// Capture the accessibility timeline
const timeline = await generator.capture(sequence);
// Inspect events
for (const event of timeline.events) {
console.log(`${event.type}: ${event.target.accessibleName}`);
}
// Check for warnings
if (timeline.warnings.length > 0) {
console.warn('Accessibility issues detected:');
timeline.warnings.forEach(w => console.warn(` - ${w.payload.message}`));
}CLI Usage
Run runtime analysis from the command line against any URL or a local Storybook instance. The CLI supports built-in interaction patterns, snapshot baselines, and CI mode for regression detection.
# Analyze a URL with default keyboard exploration
speakable runtime https://localhost:3000/settings
# Use a built-in interaction pattern
speakable runtime https://localhost:3000 --interaction modal-dialog
# Analyze Storybook components
speakable runtime http://localhost:6006 --storybook --story "Dialog*"
# Save baselines for regression detection
speakable runtime http://localhost:6006 --storybook --runtime-snapshot ./baselines
# CI mode: fail on regressions
speakable runtime http://localhost:6006 --storybook --runtime-snapshot ./baselines --runtime-ci
# Authenticate with a protected Storybook instance
speakable runtime https://storybook.internal.company.com --storybook \
--storybook-auth-header "Bearer eyJhbGciOi..."
# Skip TLS verification for self-signed certificates
speakable runtime https://storybook.local --storybook --storybook-insecureStorybook Integration
Speakable connects to a running Storybook instance, discovers stories matching your filter, and runs the runtime engine against each one. The pipeline produces an accessibility timeline per story that can be baselined and compared across builds.
How the Storybook pipeline works
When you pass --storybook, Speakable queries the Storybook index API, resolves matching stories, loads each in an isolated browser context, and executes the appropriate interaction pattern based on the component type. Each story produces a self-contained timeline. Use --runtime-snapshot to persist these timelines as baselines, then compare on subsequent runs to catch regressions automatically.
Protected and Internal Storybook Instances
Since Speakable runs locally (on your machine or CI runner), it works naturally with VPN-protected Storybook instances. As long as the machine running Speakable can reach the Storybook URL, no special configuration is needed. For Storybook instances behind authentication, use the auth flags:
# Bearer token authentication
speakable runtime https://storybook.internal.company.com --storybook \
--storybook-auth-header "Bearer your-token-here"
# Custom headers (cookies, API keys)
speakable runtime https://storybook.internal.company.com --storybook \
--storybook-header "Cookie: session=abc123"
# Self-signed TLS certificates
speakable runtime https://storybook.local:6006 --storybook --storybook-insecureVPN access: No configuration needed. The CLI fetches from your machine, which is already on the VPN.
Cloud CI runners: Use self-hosted runners with VPN access, or run npx storybook build in CI and analyze the static output.
Environment variables: Set SPEAKABLE_STORYBOOK_AUTH to avoid passing tokens on the command line.
Related Pages
Testing Strategy
Build a comprehensive accessibility testing program that catches issues early and measures progress over time.
Component Patterns
Accessible patterns for common UI components including dialogs, comboboxes, tabs, and more.
Focus Management
Control keyboard focus during dynamic interactions to keep assistive technology users oriented.
Testing Checklist
A practical checklist for verifying accessibility across components, pages, and user flows.