Accessibility tests
Web accessibility is the practice of making websites and apps accessible and inclusive to all people, regardless of ability or the technology theyβre using. That means supporting requirements such as keyboard navigation, screen reader support, sufficient color contrast, etc.
Accessibility is not only the right thing to do, but it is increasingly mandated. For example, the European accessibility act is scheduled to go into law in June 2025. Similarly in the US, laws like the Americans with Disabilities Act (ADA) and Section 508 of the Rehabilitation Act apply to many public-facing services. Many of these laws are based on WCAG, a standardized guideline for making web content accessible.
Accessibility tests audit the rendered DOM against a set of heuristics based on WCAG rules and other industry-accepted best practices. They act as the first line of QA to catch blatant accessibility violations.
Install the addon
Storybook provides an Accessibility (a11y) addon to help ensure the accessibility of your components. It is built on top of Dequeβs axe-core library, which automatically catches up to 57% of WCAG issues.
Run this command to install and configure the addon in your project:
npx storybook add @storybook/addon-a11y
Storybook's add
command automates the addon's installation and setup. To install it manually, see our documentation on how to install addons.
Your Storybook will now include some features to check the accessibility of your components, including a button in the toolbar to simulate different vision impairments and an Accessibility addon panel to check for violations.
Check for violations
When you navigate to a story, automated accessibility checks are run and the results are reported in the Accessibility addon panel.
The results are broken down into three sub-tabs:
- Violations are known violations of WCAG rules and best practices
- Passes are known non-violations
- Incomplete highlights areas that you should confirm manually because they could not be checked automatically
Configure
Because the addon is built on top of axe-core
, much of the configuration available maps to its available options:
Property | Default | Description |
---|---|---|
parameters.a11y.context | 'body' | Context passed to axe.run . Defines which elements to run checks against. |
parameters.a11y.config | (see below) | Configuration passed to axe.configure() . Most commonly used to configure individual rules. |
parameters.a11y.options | {} | Options passed to axe.run . Can be used to adjust the rulesets checked against. |
parameters.a11y.test | undefined | Determines test behavior when run with the Vitest addon. More details below. |
globals.a11y.manual | undefined | Set to true to prevent stories from being automatically analyzed when visited. More details below |
Default parameters.a11y.config
By default, Storybook disables the region rule, which does not typically apply to components in stories and can lead to false negatives.
{
rules: [
{
id: 'region',
enabled: false,
}
]
}
Weβll share examples to show how to use some of these configuration properties.
Here, they are applied to all stories in a project, in .storybook/preview.ts
:
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import { Preview } from '@storybook/your-framework';
const preview: Preview = {
parameters: {
a11y: {
/*
* Axe's context parameter
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter
* to learn more. Typically, this is the CSS selector for the part of the DOM you want to analyze.
*/
context: 'body',
/*
* Axe's configuration
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure
* to learn more about the available properties.
*/
config: {},
/*
* Axe's options parameter
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter
* to learn more about the available options.
*/
options: {},
},
},
globals: {
a11y: {
// Optional flag to prevent the automatic check
manual: true,
},
},
};
export default preview;
You can also apply the configuration for all stories in a file (in the meta
) or an individual story:
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { Button } from './Button';
const meta = {
component: Button,
parameters: {
a11y: {
/*
* Axe's context parameter
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter
* to learn more.
*/
context: {},
/*
* Axe's configuration
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure
* to learn more about the available properties.
*/
config: {},
/*
* Axe's options parameter
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter
* to learn more about the available options.
*/
options: {},
/*
* Configure test behavior
* See: https://storybook.js.org/docs/next/writing-tests/accessibility-testing#test-behavior
*/
test: 'error',
},
},
globals: {
a11y: {
// Optional flag to prevent the automatic check
manual: true,
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const ExampleStory: Story = {
parameters: {
a11y: {
// ...same config available as above
},
},
globals: {
a11y: {
// ...same config available as above
},
},
};
Rulesets
The addon uses the axe-core
library to run accessibility checks. By default, it runs a set of rules that are based on the WCAG 2.0 and 2.1 guidelines, as well as some best practices:
You can find a breakdown of these rulesets, as well as the other rulesets available in axe-coreβs documentation.
To change the rules that are checked against (e.g. to check against WCAG 2.2 AA or WCAG 2.x AAA rules), use the runOnly
option:
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import { Preview } from '@storybook/your-framework';
const preview: Preview = {
parameters: {
a11y: {
options: {
/*
* Opt in to running WCAG 2.x AAA rules
* Note that you must explicitly re-specify the defaults (all but the last array entry)
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter-examples for more details
*/
runOnly: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'best-practice', 'wcag2aaa'],
},
},
},
};
export default preview;
Individual rules
You can also enable, disable, or configure individual rules. This can be done in the config
property of the parameters.a11y
object. For example:
// ...rest of story file
export const IndividualA11yRulesExample: Story = {
parameters: {
a11y: {
config: {
rules: [
{
// The autocomplete rule will not run based on the CSS selector provided
id: 'autocomplete-valid',
selector: '*:not([autocomplete="nope"])',
},
{
// Setting the enabled option to false will disable checks for this particular rule on all stories.
id: 'image-alt',
enabled: false,
},
],
},
},
},
};
Test behavior
You can configure accessibility tests with the parameters.a11y.test
parameter, which determines the behavior of accessibility tests for a story when run with either the Vitest addon or the test-runner. The parameter accepts three values:
Value | Description |
---|---|
'off' | Do not run accessibility tests (you can still manually verify via the addon panel) |
'todo' | Run accessibility tests; violations return a warning in the Storybook UI |
'error' | Run accessibility tests; violations return a failing test in the Storybook UI and CLI/CI |
Like other parameters, you can define it at the project level in .storybook/preview.js|ts
, the component level in the default export of the story file, or the individual story level. For example, to fail on accessibility tests for all stories in a file except one:
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { Button } from './Button';
const meta = {
component: Button,
parameters: {
// π Applies to all stories in this file
a11y: { test: 'error' },
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
// π This story will use the 'error' value and fail on accessibility violations
export const Primary: Story = {
args: { primary: true },
};
// π This story will not fail on accessibility violations
// (but will still run the tests and show warnings)
export const NoA11yFail: Story = {
parameters: {
a11y: { test: 'todo' },
},
};
Why is the value called "todo" instead of "warn"? This value is intended to serve as a literal TODO
in your codebase. It can be used to mark stories that you know have accessibility issues but are not ready to fix yet. This way, you can keep track of them and address them later.
The 'off'
value should only be used for stories that do not need to be tested for accessibility, such as one used to demonstrate an antipattern in a component's usage.
You can also disable individual rules when they are not applicable to your use case.
Excluded elements
Sometimes, it may be necessary to exclude certain elements from the accessibility checks. For this, you can define a custom context to select which elements are included (or excluded) when running checks. For example, this story will ignore elements with the class no-a11y-check
:
// ...rest of story file
export const ExampleStory: Story = {
parameters: {
a11y: {
/*
* Axe's context parameter
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter
* to learn more.
*/
context: {
include: ['body'],
exclude: ['.no-a11y-check'],
},
},
},
};
Disable automated checks
When you disable automated accessibility checks, the addon will not run any tests when you navigate to a story or when you run the tests with the Vitest addon. You can still manually trigger checks in the Accessibility addon panel. This is useful for stories that are not meant to be accessible, such as those demonstrating an antipattern or a specific use case.
Disable automated accessibility checks for stories or components by adding the following globals to your story's export or component's default export:
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { MyComponent } from './MyComponent';
const meta = {
component: MyComponent,
} satisfies Meta<typeof MyComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
export const NonA11yStory: Story = {
globals: {
a11y: {
// This option disables all automatic a11y checks on this story
manual: true,
},
},
};
Run accessibility tests
With the Vitest addon
If you're using the Vitest addon, you can run your accessibility tests, as part of component tests, in these ways:
To run accessibility tests in the Storybook UI, first expand the testing widget in the sidebar and check the Accessibility checkbox. Now, when you press the Run component tests button, the accessibility tests will be run along with any other tests you have configured.
After running the tests, you will see the results in the sidebar, which will add a test status indicator next to each story that was tested. You can press on these indicators to open a menu with the Accessibility test result. Pressing on that result will navigate to that story and open the Accessibility panel, where you view details about each violation and suggestions toward how to fix them.
If any of your tests have warnings or failures, the testing widget will show the number of warnings and failures. You can press on these to filter the stories in the sidebar to only show those with warnings or failures.
In CI, accessibility tests are run automatically for stories with parameters.a11y.test = 'error'
when you run the Vitest tests.
With the test-runner
If you're using the test-runner, you can run your accessibility tests in the terminal or in CI environments.
Accessibility tests are included in your test run when you have the Accessibility addon installed and parameters.a11y.test
is set to a value other than 'off'
.
Debug accessibility violations
When you run accessibility tests, the results are reported in the Storybook UI. You can click on a violation to see more details about it, including the rule that was violated and suggestions for how to fix it.
You can also toggle on highlighting in the Storybook UI to see which elements are causing the violation, and click on a highlighted element to see the violations details in a popover menu.
Recommended workflow
You can use configuration to progressively work toward a more accessible UI by combining multiple test behaviors. For example, you can start with 'error'
to fail on accessibility violations, then switch to 'todo'
to mark components that need fixing, and finally remove the todos once all stories pass accessibility tests:
-
Update your project configuration to fail on accessibility violations by setting
parameters.a11y.test
to'error'
. This ensures that all new stories are tested to meet accessibility standards..storybook/preview.ts// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import { Preview } from '@storybook/your-framework'; const preview: Preview = { // ... parameters: { // π Fail all accessibility tests when violations are found a11y: { test: 'error' }, }, }; export default preview;
-
You will likely find that many components have accessibility failures (and maybe feel a bit overwhelmed!).
-
Take note of the components with accessibility issues and temporarily reduce their failures to warnings by applying the
'todo'
parameter value. This keeps accessibility issues visible while not blocking development. This is also a good time to commit your work as a baseline for future improvements.DataTable.stories.ts// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import { Meta } from '@storybook/your-framework'; import { DataTable } from './DataTable'; const meta = { component: DataTable, parameters: { // π This component's accessibility tests will not fail // Instead, they display warnings in the Storybook UI a11y: { test: 'todo' }, }, } satisfies Meta<typeof DataTable>; export default meta;
-
Pick a good starting point from the components you just marked
'todo'
(we recommend something like Button, for its simplicity and likelihood of being used within other components). Fix the issues in that component using the suggestions in the addon panel to ensure it passes accessibility tests, then remove the parameter.Button.stories.ts// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import { Meta } from '@storybook/your-framework'; import { Button } from './Button'; const meta = { component: Button, parameters: { // π Remove this once all stories pass accessibility tests // a11y: { test: 'todo' }, }, } satisfies Meta<typeof Button>; export default meta;
-
Pick another component and repeat the process until you've covered all your components and you're an accessibility hero!
FAQ
Whatβs the difference between browser-based and linter-based accessibility tests?
Browser-based accessibility tests, like those found in Storybook, evaluate the rendered DOM because that gives you the highest accuracy. Auditing code that hasn't been compiled yet is one step removed from the real thing, so you won't catch everything the user might experience.
Why are my tests failing in different environments?
With the Vitest addon, your tests run in Vitest using your project's configuration with Playwright's Chromium browser. This can lead to inconsistent test results reported in the Storybook UI or CLI. The inconsistency can be due to axe-core
reporting different results in different environments, such as browser versions or configurations. If you encounter this issue, we recommend reaching out using the default communication channels (e.g., GitHub discussions, Github issues).
The addon panel does not show expected violations
Modern React components often use asynchronous techniques like Suspense or React Server Components (RSC) to handle complex data fetching and rendering. These components donβt immediately render their final UI state. Storybook doesnβt inherently know when an async component has fully rendered. As a result, the a11y checks sometimes run too early, before the component finishes rendering, leading to false negatives (no reported violations even if they exist).
To address this issue, we have introduced a feature flag: developmentModeForBuild
. This feature flag allows you to set process.env.NODE_ENV
to 'development'
in built Storybooks, enabling development-related optimizations that are typically disabled in production builds. One of those development optimizations is Reactβs act
utility, which helps ensure that all updates related to a test are processed and applied before making assertions, like a11y checks.
To enable this feature flag, add the following configuration to your .storybook/main.js|ts
file:
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { StorybookConfig } from '@storybook/your-framework';
const config: StorybookConfig = {
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
features: {
developmentModeForBuild: true,
},
};
export default config;
More testing resources
- Vitest addon for running tests in Storybook
- Interaction testing for user behavior simulation
- Visual testing for appearance
- Snapshot testing for rendering errors and warnings
- Test coverage for measuring code coverage
- CI for running tests in your CI/CD pipeline
- End-to-end testing for simulating real user scenarios
- Unit testing for functionality
- Test runner to automate test execution