Storybook 无障碍测试指南:在单个 Story 中按规则精细化配置 axe-core 检查参数
导读
Storybook 的 Accessibility(a11y)Addon 基于 Deque 的 axe-core 库对渲染后的 DOM 执行无障碍审计。实际项目中,不同 Story 往往面临不同的无障碍约束——某些表单控件天然携带 autocomplete 属性、某些演示反模式的 Story 无需 image-alt 检查。此时项目级配置无法满足需求,需要把 parameters.a11y.config 的规则级配置落到单个 Story 层级。本篇以 Storybook 仓库文档片段 addon-a11y-config-rules-in-story.md 为主线,讲解如何在单条 Story 内通过 config.rules 数组按 id 开关、限定规则的 axe-core 规则,并覆盖 CSF 3、CSF Next 与 Svelte CSF 三种写法,最后结合仓库源码说明规则配置的底层合并逻辑。
一、先理解参数的层级:Story 级 config 覆盖什么
axe-core 的配置可以通过 Storybook 的 parameters 从粗到细分三层注入:
| 配置位置 | 作用范围 | 典型文件 |
|---|---|---|
.storybook/preview.* 的 parameters.a11y |
项目内全部 Story | 项目级全局规则,如统一关闭 region |
Story 文件 meta(default export)的 parameters.a11y |
该文件内全部 Story | 组件级默认 |
单个 Story 的 parameters.a11y |
仅该条 Story | 局部开关规则,见 addon-a11y-config-in-meta-and-story.md |
parameters.a11y 中与 axe-core 相关的三个核心字段在源码 params.ts 中被定义为 A11yParameters 接口:
context:传给axe.run的上下文,决定对哪些元素运行检查(文档默认值为'body',详见 accessibility-testing.mdx 配置表);config:传给axe.configure()的配置对象(即 axe-core 的Spec),最常见的用途就是按规则配置(individual rules);options:传给axe.run的选项,可用于调整runOnly规则集。
本篇讨论的单个 Story 中的 config.rules 即属于 config 字段。需要强调的是:axe-core 的 Spec.rules 中每条规则对象可以携带 id、enabled、selector、any/all/none、tags 等属性,Storybook 场景下最常用的组合是 id + enabled(整条规则开/关)与 id + selector(把规则限定在指定 CSS 选择器命中的元素上)。
二、核心示例:在单个 Story 中配置两条规则
原文档给出的是同一个示例在 11 种写法变体下的完整代码,其技术实质是两条规则:
config: {
rules: [
{
// 基于提供的 CSS 选择器,autocomplete 规则将不会在命中的元素上运行
id: 'autocomplete-valid',
selector: '*:not([autocomplete="nope"])',
},
{
// 将 enabled 设为 false,会在该条 Story 上关闭此项规则的检查
id: 'image-alt',
enabled: false,
},
],
}
两条规则分别演示了两种典型的“按规则定制”手段:
autocomplete-valid配合selector: '*:not([autocomplete="nope"])':当被测组件里恰好存在一个值为nope(反模式演示)的输入框时,这条 CSS 选择器把它从自动完成校验中排除出去,其余输入框仍正常受检;image-alt直接设enabled: false:彻底关闭图片替代文本的检查——适合图片本身只是装饰性元素、或当前 Story 刻意演示缺失 alt 的场景。
与默认禁用规则的区别
需要注意一个前提:Storybook 对全部检查默认就已关闭了 region 规则。在 accessibility-testing.mdx 的 “Default parameters.a11y.config” 说明与 a11yRunner.ts 的 DISABLED_RULES 常量中可以看到,原因是组件测试中 landmarks(地标元素)未必存在,检查会产生误报。因此你在 Story 里配置的 rules 是在“默认关闭 region”之上的增量定制,而不是从零重建规则全集。
三、按渲染器与语法选择你的写法
下面按写法族完整给出可运行的示例。
3.1 CSF 3 + TypeScript(common / React / Vue 等)
适用于 React、Vue 等主流渲染器的 Button.stories.ts,import 行换成对应框架(如 @storybook/react-vite、@storybook/vue3-vite):
// ...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,
},
],
},
},
},
};
3.2 CSF 3 + JavaScript
// ...rest of story file
export const IndividualA11yRulesExample = {
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,
},
],
},
},
},
};
3.3 CSF Next 🧪(preview.meta / meta.story)
CSF Next 通过从 .storybook/preview 导入的 preview.meta() 声明 meta、meta.story() 声明 Story,参数结构保持不变。以 React 为例:
import preview from '../.storybook/preview';
import Button from './Button';
const meta = preview.meta({
component: Button,
});
export const IndividualA11yRulesExample = meta.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,
},
],
},
},
},
});
其它渲染器的差异仅在导入与 component 的写法上,parameters.a11y.config.rules 本身完全一致:
- Angular:
import { Button } from './button.component';,component: Button; - Vue:
import Button from './Button.vue';,component: Button; - Web Components:无需导入组件类,直接
const meta = preview.meta({ component: 'demo-button' });。
3.4 Svelte CSF(defineMeta + Story 标签)
Svelte CSF 是唯一语法形态不同的变体,meta 通过 <script module> 里的 defineMeta 定义,Story 通过模板中的 <Story> 标签声明,parameters 以对象字面量形式传入:
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Button from './Button.svelte';
const { Story } = defineMeta({
component: Button,
});
</script>
<Story
name="IndividualA11yRulesExample"
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,
},
],
},
},
}}
/>
若你的 Svelte 项目使用普通 CSF 3 而非 Svelte CSF,则把同一份 parameters 对象放入具名导出的 Story 即可,与本篇 3.1 / 3.2 节写法相同。
四、底层原理:Story 级 rules 是如何被合并执行的
为什么把 rules 写在单条 Story 里就能只影响这一条?答案在 a11yRunner.ts 的 run() 函数中,它负责把 A11yParameters 转成对 axe-core 的真正调用:
- 合并默认禁用规则:
configWithDefault把默认的DISABLED_RULES(即region,{ id: 'region', enabled: false })拼接到用户传入的config.rules之前,再调用axe.configure(configWithDefault)。由于你写在 Story 上的rules来自当前 Story 的 parameters,故每条 Story 运行时都会带着自己的规则集去重新axe.reset()+axe.configure(); - 按顺序覆盖:
getDisabledRules()遍历config.rules时,注释明确写着 “Rules are applied in order, so a later entry overrides an earlier one for the same id”——同一条 id 的规则,数组中靠后的条目覆盖靠前的条目,这一语义与 axe-core 的规则合并行为一致; - runOnly 下的镜像禁用:
mergeDisabledRulesIntoRunOptions()处理一个易被忽略的细节——当options里配置了runOnly(例如只想按 WCAG 2.2 AA 规则集检查)时,axe.run({ runOnly })可能会重新启用某些已关闭规则的 tag 匹配。因此该函数会把你显式enabled: false的规则镜像写进axe.run的rules选项,保证禁用规则不被 runOnly 悄悄复活; - 排除内部元素与串行队列:axe-core 运行前会把 Storybook 内部元素(
.sb-wrapper、#storybook-docs、#storybook-highlights-root)加入 exclude 上下文,且通过一个 Promise 队列把多次检查串行化,因为 axe-core 并不适合并行执行。
仓库中的单元测试 a11yRunner.test.ts 直接验证了上述行为:测试 passes disabled configured rules to axe.run when runOnly is present 断言 config.rules 中 { id: 'target-size', enabled: false } 会同时出现在传给 axe.configure 的 rules 与传给 axe.run 的 rules.target-size = { enabled: false } 中;测试 respects configured rule overrides when collecting disabled rules 则验证了把默认的 region 显式设为 enabled: true 时,runner 不会再向 runOnly 注入禁用项。
五、常见搭配与注意点
- 与 runOnly 规则集配合:若希望在项目级通过
options.runOnly切换检查规则集(如 WCAG 2.2 AA),请参考 addon-a11y-config-rulesets-in-preview.md;而单条 Story 的config.rules适合做规则级的例外豁免,两者互不冲突; - 与
parameters.a11y.test配合:Story 层配置还可以配合parameters.a11y.test(取值'off'/'todo'/'error')决定该 Story 的可访问性测试是跳过、作为 TODO 警告还是直接失败。规则豁免应只服务于“确实不需要检查”的合理场景,而不是用它来掩盖未修复的违规——可访问性面板仍会展示 Violations 结果; - selector 的取舍:能用
selector把误报元素精准排除时,优先于全局enabled: false,因为它保留了该规则对 Story 其余元素的覆盖; - 组件级与文件级同理:同一份
config.rules也可以上移到 meta 级别作用于整个文件,写法参考 addon-a11y-config-in-meta-and-story.md; - 手动触发不受影响:规则配置只影响自动检查与 Vitest addon 的测试行为,你仍可在 Accessibility 面板中手动运行检查查看 Violations / Passes / Incomplete 三个子标签的结果。
六、小结
Storybook 的 a11y 规则配置是自顶向下继承、按层级合并生效的:项目级预览文件、文件级 meta、单条 Story 三级均可设置 parameters.a11y.config.rules。在本篇展示的 autocomplete-valid + selector 与 image-alt + enabled: false 两个组合中,你可以掌握“按 CSS 选择器局部豁免某规则”与“整条规则关闭”两种精细化手段,并能在 CSF 3、CSF Next 与 Svelte CSF 之间自由迁移写法。结合 a11yRunner.ts 的合并逻辑可知:同 id 的 rules 按数组顺序后者覆盖前者,配合 runOnly 时禁用规则会被镜像到 run 选项以保证不被重新启用——理解了这些底层规则,你就能在真实组件库中把自动化无障碍检查的误报降到最低,同时又不牺牲规则覆盖面。
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