首页
/ Storybook 无障碍全局配置实战:在 .storybook/preview 中配置 a11y 插件的 context、config、options 与手动检测

Storybook 无障碍全局配置实战:在 .storybook/preview 中配置 a11y 插件的 context、config、options 与手动检测

2026-09-06 18:36:36作者:裘晴惠Vivianne

本篇文章围绕 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.a11ycontextconfigoptions 字段与 globals(CSF3 中写作 initialGlobals)下 a11y.manual 标记。把它们写在 .storybook/preview.* 中,意味着配置对当前项目里所有组件、所有故事生效,是最常用也最符合直觉的"一次配置、全局使用"方式。

二、配置总览:参数骨架与三层作用域

a11y 相关配置可以出现在三个层级,优先级从低到高为:

  1. 项目级:.storybook/preview.*(本文主题,作用于所有故事);
  2. 组件级:故事文件中的 metaexport default)对象;
  3. 单个故事级:具体某个 story 的导出对象。

三层配置通过 Storybook 的参数合并机制叠加,越具体的层级越能覆盖全局配置。组件级与故事级的写法可参考 addon-a11y-config-in-meta-and-story.md,本文聚焦项目级全局写法。

全局骨架可以用一张参数表概括:

字段 所在位置 作用 与 axe-core 的对应关系
context parameters.a11y.context 限定对 DOM 的哪一部分执行检测,可写成 CSS 选择器字符串,也可写成带 include/exclude 的对象 axe-core axe.runcontext 参数
config parameters.a11y.config 全局规则配置,如启用/禁用/调整单条规则 axe.configure()configuration
options parameters.a11y.options 每次运行时的选项,最典型的是用 runOnly 更换规则集(例如追加 WCAG 2.x AAA) axe.runoptions 参数
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?: ContextSpecWithoutNodeparams.ts):axe context 的拷贝类型,支持选择器或 { include, exclude } 对象;
  • options?: RunOptionsparams.ts):直接复用 axe-core 的 RunOptions 类型;
  • config?: Specparams.ts):直接复用 axe-core 的 Spec 配置类型;
  • disable?: booleantest?: 'off' | 'todo' | 'error'params.ts)则是插件在 axe 之上的封装。

manual 标记则定义在 types.tsA11yGlobals 接口中:置为 true 可阻止插件在访问故事时自动执行检测,但你仍可从插件面板手动运行检查。

三、CSF3 写法:在 preview 中声明 a11y 参数

在 CSF3 下,项目级的无障碍配置是一个普通的 export default 配置对象,同时声明 parameters.a11yinitialGlobals.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: trueinitialGlobals 背后的运行逻辑

原片段中 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;无违规则统一上报 passedpreview.tsx)。因此在 Vitest 独立运行时,违规且模式为 'error' 会抛出 toHaveNoViolations 断言失败,让 CI 变红。

推荐渐进式落地方式:先在项目级把 test 设为 'error',保证新故事全部达到无障碍标准;对历史存量组件,在其所在 story 上临时标记 'todo'(片段示例见 addon-a11y-parameter-todo-in-meta.md),逐项修复后再移除标记。全局报错但仅个别故事豁免的写法参见 addon-a11y-parameter-error-in-preview.mdaddon-a11y-parameter-remove.md

八、模块级禁用的补充:disable

如果某个组件整体都不该被自动检测(例如专用于展示无障碍反模式的组件库),还可以在故事或 meta 层设置 parameters.a11y.disable = true。从 preview.tsx 的执行条件可以看到 disable !== true 是自动检测的前置条件之一,具体用法见 addon-a11y-disable.md

九、相关源码与文档索引

如果你想进一步确认底层行为或查阅更多层级的写法,可以继续深入以下位置:

小结:把无障碍配置收敛到 .storybook/preview.* 是一项低成本、高收益的工程实践。它用 context 圈定检测范围、用 config 调整规则、用 options 切换规则集,再用 manualtest 控制自动检测与测试判级,从而让无障碍审计成为组件开发流程中稳定、可预期的一环。

登录后查看全文
热门项目推荐
相关项目推荐