Framework Guides
Integrate Speakable into your React, Angular, Svelte, or Web Component workflow. Each guide shows how to extract rendered HTML from your components and feed it into Speakable for screen reader simulation.
React
Use @testing-library/react to render components, then pass the HTML to Speakable for analysis.
Unit test integration
Render your component in a test, extract the HTML, and run Speakable programmatically:
import { render } from '@testing-library/react';
import { parseHTML, buildAccessibilityTree, renderNVDA } from '@reticular/speakable';
import { SubmitButton } from './SubmitButton';
test('button announces correctly for NVDA', () => {
const { container } = render(<SubmitButton label="Place Order" />);
const html = container.innerHTML;
const doc = parseHTML(html);
const { model } = buildAccessibilityTree(doc.document.body);
const output = renderNVDA(model);
expect(output).toContain('Place Order, button');
});Storybook integration
Export rendered HTML from Storybook stories and analyze with the CLI:
# Build Storybook static HTML, then analyze
npx storybook build -o storybook-static
speakable storybook-static/iframe.html --selector ".sb-show-main" -f auditNext.js pages
For Next.js, build the site and analyze the static HTML output:
next build
speakable out/index.html -f audit -s all
# Analyze a specific page
speakable out/pricing.html --selector "main" -f textAngular
Use Angular's TestBed to render components, then extract the native element HTML for analysis.
Component test integration
import { TestBed } from '@angular/core/testing';
import { parseHTML, buildAccessibilityTree, renderNVDA } from '@reticular/speakable';
import { SubmitButtonComponent } from './submit-button.component';
describe('SubmitButton a11y', () => {
it('announces correctly for NVDA', () => {
TestBed.configureTestingModule({
declarations: [SubmitButtonComponent]
});
const fixture = TestBed.createComponent(SubmitButtonComponent);
fixture.componentInstance.label = 'Place Order';
fixture.detectChanges();
const html = fixture.nativeElement.innerHTML;
const doc = parseHTML(html);
const { model } = buildAccessibilityTree(doc.document.body);
const output = renderNVDA(model);
expect(output).toContain('Place Order, button');
});
});Build output analysis
ng build
speakable dist/my-app/browser/index.html -f auditSvelte
Use @testing-library/svelte to render components, then pass the HTML to Speakable. For SvelteKit apps, analyze the static adapter output directly.
Component test integration
Render your Svelte component in a test and extract the HTML for analysis:
import { render } from '@testing-library/svelte';
import { parseHTML, buildAccessibilityTree, renderNVDA, renderVoiceOver } from '@reticular/speakable';
import SubmitButton from './SubmitButton.svelte';
test('button announces correctly for NVDA', () => {
const { container } = render(SubmitButton, {
props: { label: 'Place Order' }
});
const html = container.innerHTML;
const doc = parseHTML(html);
const { model } = buildAccessibilityTree(doc.document.body);
expect(renderNVDA(model)).toContain('Place Order, button');
});
test('VoiceOver announces role first for landmarks', () => {
const { container } = render(NavBar, {
props: { label: 'Main' }
});
const doc = parseHTML(container.innerHTML);
const { model } = buildAccessibilityTree(doc.document.body);
// VoiceOver puts role before name for landmarks
expect(renderVoiceOver(model)).toContain('navigation, Main');
});SvelteKit static output
If you're using SvelteKit with the static adapter, analyze the build output directly:
# Build with static adapter
npm run build
# Analyze the generated pages
speakable build/index.html -f audit -s all
# Analyze a specific route
speakable build/about/index.html --selector "main" -f text
# Batch analyze all pages
speakable --batch build/**/index.html -f auditSvelte 5 with runes
The same pattern works with Svelte 5 — the testing library renders the component to DOM regardless of whether you use runes or legacy syntax:
import { render } from '@testing-library/svelte';
import { parseHTML, buildAccessibilityTree, renderNVDA } from '@reticular/speakable';
import Dialog from './Dialog.svelte';
test('dialog announces with title and description', () => {
const { container } = render(Dialog, {
props: {
open: true,
title: 'Delete account?',
description: 'This cannot be undone.'
}
});
const doc = parseHTML(container.innerHTML);
const { model } = buildAccessibilityTree(doc.document.body);
const output = renderNVDA(model);
expect(output).toContain('Delete account?, dialog');
expect(output).toContain('This cannot be undone.');
});Web Components
Web Components render to the light DOM or shadow DOM. Speakable analyzes the light DOM output — extract innerHTML or use shadowRoot.innerHTML for shadow DOM components.
Lit component testing
import { fixture, html } from '@open-wc/testing';
import { parseHTML, buildAccessibilityTree, renderNVDA } from '@reticular/speakable';
import './my-button.js';
it('announces correctly', async () => {
const el = await fixture(html`<my-button label="Submit"></my-button>`);
// For light DOM components
const output = el.innerHTML;
// For shadow DOM components
// const output = el.shadowRoot.innerHTML;
const doc = parseHTML(output);
const { model } = buildAccessibilityTree(doc.document.body);
expect(renderNVDA(model)).toContain('Submit, button');
});Vanilla Web Components
import { parseHTML, buildAccessibilityTree, renderVoiceOver } from '@reticular/speakable';
// Create and render the component
const el = document.createElement('my-dialog');
el.setAttribute('open', '');
el.innerHTML = '<h2>Confirm</h2><button>OK</button>';
document.body.appendChild(el);
// Analyze
const doc = parseHTML(el.outerHTML);
const { model } = buildAccessibilityTree(doc.document.body);
console.log(renderVoiceOver(model));
// "web dialog
heading level 2, Confirm
OK, button"General Pattern
Regardless of framework, the integration pattern is the same:
- Render your component to HTML (via test harness, build output, or DOM API)
- Pass the HTML string to
parseHTML() - Build the accessibility tree with
buildAccessibilityTree() - Render with your target screen reader (
renderNVDA,renderJAWS,renderVoiceOver) - Assert against expected output or save as a baseline for regression detection
The CLI works with any static HTML file. If your framework produces HTML output (SSG, SSR, or build artifacts), you can analyze it directly without writing any integration code: speakable dist/index.html -f audit