Storybook 无障碍全局配置实战:在 .storybook/preview 中配置 a11y 插件的 context、config、options 与手动检测
本篇文章围绕 Storybook 内置无障碍(Accessibility,a11y)插件给出的全局配置骨架展开,说明如何在项目根级的 .storybook/preview.* 中一次性声明 parameters.a11y.context / config / options,并通过 initialGlobals.a11y.manual 切换"访问故事即自动扫描"与"仅手动触发检测"两种行为。读完本文,你将掌握 CSF3 与 CSF Next 两套写法下的全局无障碍配置、各参数与 axe-core 底层 API 的映射关系,以及配置生效的层级与源码依据。
本文对应仓库中的代码片段文档为 addon-a11y-config-in-preview.md,相关插件的完整使用说明位于 accessibility-testing.mdx。
一、为什么要把 a11y 配置写在 preview 里
Storybook 的无障碍检测能力由 @storybook/addon-a11y 提供,它基于 Deque 的 axe-core 库对渲染后的 DOM 执行自动化审计。在正式使用之前,通常需要回答三个问题:
- 检测哪些元素(页面上的哪一部分 DOM);
- 用哪些规则与规则集去检测(默认的 WCAG 2 级规则、是否追加 AAA 规则等);
- 是否在每次进入故事时自动执行。
这三个问题的答案分别对应 parameters.a11y 的 context、config、options 字段与 globals(CSF3 中写作 initialGlobals)下 a11y.manual 标记。把它们写在 .storybook/preview.* 中,意味着配置对当前项目里所有组件、所有故事生效,是最常用也最符合直觉的"一次配置、全局使用"方式。
二、配置总览:参数骨架与三层作用域
a11y 相关配置可以出现在三个层级,优先级从低到高为:
- 项目级:
.storybook/preview.*(本文主题,作用于所有故事); - 组件级:故事文件中的
meta(export default)对象; - 单个故事级:具体某个 story 的导出对象。
三层配置通过 Storybook 的参数合并机制叠加,越具体的层级越能覆盖全局配置。组件级与故事级的写法可参考 addon-a11y-config-in-meta-and-story.md,本文聚焦项目级全局写法。
全局骨架可以用一张参数表概括:
| 字段 | 所在位置 | 作用 | 与 axe-core 的对应关系 |
|---|---|---|---|
context |
parameters.a11y.context |
限定对 DOM 的哪一部分执行检测,可写成 CSS 选择器字符串,也可写成带 include/exclude 的对象 |
axe-core axe.run 的 context 参数 |
config |
parameters.a11y.config |
全局规则配置,如启用/禁用/调整单条规则 | axe.configure() 的 configuration |
options |
parameters.a11y.options |
每次运行时的选项,最典型的是用 runOnly 更换规则集(例如追加 WCAG 2.x AAA) |
axe.run 的 options 参数 |
test |
parameters.a11y.test |
与 Vitest addon / test-runner 配合时决定违规行为:'off' 不跑、'todo' 警告、'error' 判失败 |
addon 测试行为开关 |
disable |
parameters.a11y.disable |
true 时整体关闭无障碍自动检测 |
addon 专属开关 |
manual |
globals.a11y.manual(CSF3 里配在 initialGlobals.a11y.manual) |
true 时进入故事不再自动扫描,但仍可在无障碍面板手动触发 |
addon 专属开关 |
其中 context/config/options 的类型定义可在插件源码 params.ts 中看到:
context?: ContextSpecWithoutNode(params.ts):axe context 的拷贝类型,支持选择器或{ include, exclude }对象;options?: RunOptions(params.ts):直接复用 axe-core 的RunOptions类型;config?: Spec(params.ts):直接复用 axe-core 的Spec配置类型;disable?: boolean与test?: 'off' | 'todo' | 'error'(params.ts)则是插件在 axe 之上的封装。
manual 标记则定义在 types.ts 的 A11yGlobals 接口中:置为 true 可阻止插件在访问故事时自动执行检测,但你仍可从插件面板手动运行检查。
三、CSF3 写法:在 preview 中声明 a11y 参数
在 CSF3 下,项目级的无障碍配置是一个普通的 export default 配置对象,同时声明 parameters.a11y 与 initialGlobals.a11y。
3.1 通用 JS / JSX 写法
export default {
parameters: {
a11y: {
/*
* Axe's context parameter
* Typically, this is the CSS selector for the part of the DOM you want to analyze.
*/
context: 'body',
/*
* Axe's configuration
*/
config: {},
/*
* Axe's options parameter
*/
options: {},
},
},
initialGlobals: {
a11y: {
// Optional flag to prevent the automatic check
manual: true,
},
},
};
3.2 带类型约束的 TS / TSX 写法
在 TypeScript 项目中,推荐把配置对象标注为框架对应的 Preview 类型,从而让 parameters.a11y 的字段获得完整的类型提示与校验:
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Preview } from '@storybook/your-framework';
const preview: Preview = {
parameters: {
a11y: {
context: 'body',
config: {},
options: {},
},
},
initialGlobals: {
a11y: {
// Optional flag to prevent the automatic check
manual: true,
},
},
};
export default preview;
四、CSF Next 写法:通过 definePreview 装配 addonA11y
在 Storybook 实验性的 CSF Next 体系下,.storybook/preview.* 不再导出裸对象,而是通过框架提供的 definePreview() 声明式地注册插件与参数:addons 数组传入 addonA11y(),配置对象体与 CSF3 完全一致。根据你使用的渲染器不同,仅 definePreview 的导入来源有差异。
4.1 React(.storybook/preview.tsx / .jsx)
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { definePreview } from '@storybook/your-framework';
import addonA11y from '@storybook/addon-a11y';
export default definePreview({
addons: [addonA11y()],
parameters: {
a11y: {
context: 'body',
config: {},
options: {},
},
},
initialGlobals: {
a11y: {
// Optional flag to prevent the automatic check
manual: true,
},
},
});
JS 场景等价于把 .tsx 换成 .jsx,其余内容不变。
4.2 Vue 3(.storybook/preview.ts)
import { definePreview } from '@storybook/vue3-vite';
import addonA11y from '@storybook/addon-a11y';
export default definePreview({
addons: [addonA11y()],
parameters: {
a11y: {
context: 'body',
config: {},
options: {},
},
},
initialGlobals: {
a11y: {
// Optional flag to prevent the automatic check
manual: true,
},
},
});
Vue 项目同样提供 .storybook/preview.js 的 JavaScript 等价写法。
4.3 Angular(.storybook/preview.ts)
import { definePreview } from '@storybook/angular';
import addonA11y from '@storybook/addon-a11y';
export default definePreview({
addons: [addonA11y()],
parameters: {
a11y: {
context: 'body',
config: {},
options: {},
},
},
initialGlobals: {
a11y: {
// Optional flag to prevent the automatic check
manual: true,
},
},
});
4.4 Web Components(.storybook/preview.ts)
import { definePreview } from '@storybook/web-components-vite';
import addonA11y from '@storybook/addon-a11y';
export default definePreview({
addons: [addonA11y()],
parameters: {
a11y: {
context: 'body',
config: {},
options: {},
},
},
initialGlobals: {
a11y: {
// Optional flag to prevent the automatic check
manual: true,
},
},
});
web-components 同样有 preview.js 的 JavaScript 版本。原片段文件 addon-a11y-config-in-preview.md 中每个 CSF Next 框架都并列给出了 TS 与 JS 两种 tab,其差别仅在于文件扩展名与 definePreview 的导入框架,配置语义完全一致。
五、三个 axe-core 参数逐一拆解
5.1 context:检测范围
context 直接对应传入 axe-core axe.run 的 context 参数,本质是"要对 DOM 的哪部分做检查"。最常见写法是 'body'(对整个页面执行,见上文所有示例),也可以是一个更精确的 CSS 选择器。
当需要精确地包含或排除某些区域时,使用对象形式:
context: {
include: ['body'],
exclude: ['.no-a11y-check'],
}
上面这种 { include, exclude } 结构常用于在故事级忽略带某个类名(例如 no-a11y-check)的演示性、反模式元素,完整的分层示例见 addon-a11y-config-context-in-story.md。
5.2 config:规则级别的全局配置
config 对应 axe.configure(),通常用于细粒度地控制规则本身——例如关闭某条内置规则、为某条规则调整判定标签,或注册自定义规则。它配置的是"规则"而非"运行选项"。若需要在故事级按规则启用/禁用/修改单条规则,参考片段 addon-a11y-config-rules-in-story.md。
5.3 options:运行选项与规则集
options 对应 axe.run 的 options 参数,最常用的是通过 runOnly 决定检测的规则集。插件默认只跑部分规则,若要主动纳入 WCAG 2.x AAA 级别规则,必须在 runOnly 数组中显式地把默认规则重新列出并追加 'wcag2aaa',否则会覆盖掉默认集合。示例见 addon-a11y-config-rulesets-in-preview.md:
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)
*/
runOnly: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'best-practice', 'wcag2aaa'],
},
六、manual: true 与 initialGlobals 背后的运行逻辑
原片段中 manual: true 被放在 initialGlobals.a11y 下。之所以用 initialGlobals 而不是 globals,是因为在 CSF3 及以后的故事初始化阶段,全局变量通过 initialGlobals 声明;而在组件/故事层级的 CSF3 对象中,相关片段文档(如 addon-a11y-config-in-meta-and-story.md)使用的是 globals 键。这两处仅是声明位置的差异,最终都汇入运行时的 globals.a11y.manual。
manual: true 的真实效果可以在插件源码中验证。在 preview.tsx 中,插件通过 afterEach 钩子在每个故事渲染完成后决定是否执行检测:
const shouldRunEnvironmentIndependent =
!isGhostStories &&
a11yParameter?.disable !== true &&
a11yParameter?.test !== 'off' &&
a11yGlobals?.manual !== true;
也就是说,只要满足以下任意一条,插件就会跳过自动检测:
- 当前是 ghost stories 运行(
globals.ghostStories为真,此时运行无意义); parameters.a11y.disable === true;parameters.a11y.test === 'off';globals.a11y.manual === true(即片段里演示的场景)。
manual: true 的定位是:进入故事时不自动审计,但检测仍可从无障碍面板手动触发。它适合那些刻意演示反模式、不能也不应通过自动检查的故事,避免它们每进一次就打一次违规报告。
值得补充的是,插件在 preview.tsx 中还导出了自身默认的 parameters.a11y.test(源码中为 'todo')与 initialGlobals.a11y.manual,用户项目级配置会在此基础上覆盖。
七、test 行为:接入测试体系时的违规处理策略
parameters.a11y.test 决定无障碍检测的结果如何进入 Storybook 测试体系(Vitest addon 或 test-runner),取值为三种:
| 取值 | 行为 |
|---|---|
'off' |
不执行无障碍测试(仍可在面板手动检测) |
'todo' |
执行检测,违规以警告形式显示在 Storybook UI 中 |
'error' |
执行检测,违规在 Storybook UI 与 CLI/CI 中表现为失败 |
从 preview.tsx 的实现看,插件会把 test 映射为上报状态:
switch (a11yParameter?.test) {
case 'todo':
return 'warning';
case 'error':
default:
return 'failed';
}
当存在违规(result.violations.length > 0)时,'todo' 上报为 warning,'error' 上报为 failed;无违规则统一上报 passed(preview.tsx)。因此在 Vitest 独立运行时,违规且模式为 'error' 会抛出 toHaveNoViolations 断言失败,让 CI 变红。
推荐渐进式落地方式:先在项目级把 test 设为 'error',保证新故事全部达到无障碍标准;对历史存量组件,在其所在 story 上临时标记 'todo'(片段示例见 addon-a11y-parameter-todo-in-meta.md),逐项修复后再移除标记。全局报错但仅个别故事豁免的写法参见 addon-a11y-parameter-error-in-preview.md 与 addon-a11y-parameter-remove.md。
八、模块级禁用的补充:disable
如果某个组件整体都不该被自动检测(例如专用于展示无障碍反模式的组件库),还可以在故事或 meta 层设置 parameters.a11y.disable = true。从 preview.tsx 的执行条件可以看到 disable !== true 是自动检测的前置条件之一,具体用法见 addon-a11y-disable.md。
九、相关源码与文档索引
如果你想进一步确认底层行为或查阅更多层级的写法,可以继续深入以下位置:
- 参数类型定义:
context/options/config/disable/test全部字段见 code/addons/a11y/src/params.ts; - globals 类型:
a11y.manual见 code/addons/a11y/src/types.ts; - 自动检测触发逻辑:afterEach 钩子与状态映射见 code/addons/a11y/src/preview.tsx;
- 检测执行入口:
run函数见 code/addons/a11y/src/a11yRunner.ts; - 完整接入说明:安装(
storybook add @storybook/addon-a11y,片段见 addon-a11y-add.md)、参数语义与测试行为见 accessibility-testing.mdx; - 其他层级与变体写法:addon-a11y-config-in-meta-and-story.md、addon-a11y-config-context-in-story.md、addon-a11y-config-rules-in-story.md、addon-a11y-config-rulesets-in-preview.md。
小结:把无障碍配置收敛到 .storybook/preview.* 是一项低成本、高收益的工程实践。它用 context 圈定检测范围、用 config 调整规则、用 options 切换规则集,再用 manual 与 test 控制自动检测与测试判级,从而让无障碍审计成为组件开发流程中稳定、可预期的一环。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00