Iteration 175 §3 — single source of truth for the project's a11y baseline, the
Critical_Page_Setscan results, and any "necessary exemptions" that are tolerated under the AA gate.
A11y compliance is not a one-time fix; the baseline is enforced on every PR by
the frontend-a11y CI job. This document explains:
- which pages are scanned;
- the WCAG conformance level (2.1 AA);
- what
criticalandseriousaxe violations get blocked; - the limited list of exemptions, each with the WCAG clause being deferred and the reason.
The 7 pages enumerated in requirements.md Requirement 3.1:
| # | Route | View module | Auth required |
|---|---|---|---|
| 1 | /login |
views/auth/Login* |
no |
| 2 | /dashboard |
views/Dashboard* |
yes |
| 3 | /ai-chat |
views/AIChatPage |
yes |
| 4 | /backtests |
views/BacktestList* (or equivalent) |
yes |
| 5 | /backtests/:id |
views/BacktestDetail* |
yes |
| 6 | /knowledge-base |
views/KnowledgeBasePage |
yes |
| 7 | /strategies |
views/Strategy* |
yes |
- WCAG 2.1 Level AA, scanned with axe-core via
@axe-core/playwrightusing.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']). - Lighthouse Accessibility category threshold:
>= 0.9(90/100). Seeconfig/lighthouserc.js.
critical and serious axe violations fail the frontend-a11y CI job and
block PR merge. minor and moderate are surfaced as warnings.
These are baseline expectations every page in the Critical_Page_Set must already satisfy. Any of the following on a covered page is treated as a serious violation by axe and will block PR merge:
| # | Constraint | Example fix |
|---|---|---|
| a | Non-decorative <img> / icon must have alt or aria-label |
Add aria-label="刷新数据" to icon-only refresh button |
| b | All form inputs have explicit <label> or aria-labelledby |
Wrap <el-input> with an Element Plus form-item that has a label |
| c | Color contrast ≥ 4.5:1 normal / 3:1 large text & graphics | Use design system colour tokens, see DESIGN_SYSTEM.md |
| d | All interactive elements reachable via Tab / Shift+Tab; focus ring visible | Avoid outline: none without a replacement focus indicator |
| e | Modal opens trap focus; close returns focus to invoker | Use Element Plus <el-dialog> (handles trap automatically) |
"Non-decorative" is identified as: an image element that does not carry
any of role="presentation", aria-hidden="true", or empty alt="".
Hard cap: ≤ 5 entries (Requirement 3.8). Each entry must reference a WCAG clause and be reviewed annually.
| # | Page / Component | WCAG clause | Reason | Owner | Annual review |
|---|---|---|---|---|---|
| none | — | — | At 175 entry, no exemption registered. New entries require an |
RFC and CODEOWNERS approval. | — | — |
175 推进过程中已在以下组件应用了 a11y 修复,作为 frontend-a11y job 首次跑前的预清理:
- 侧边栏
<el-aside>添加role="navigation"与aria-label="主导航" - 移动端汉堡按钮:
<div @click>→<button type="button">,附aria-label、aria-controls、aria-expanded - 移动端抽屉关闭图标:
<el-icon @click>→<button type="button">附aria-label - 移动端抽屉本体:补
role="dialog"、aria-modal="true"、aria-label、id(与aria-controls对应) - 用户下拉触发器:
<span>→<button type="button">附aria-label - 主内容
<el-main>添加role="main" - 装饰性图标(侧边栏品牌图标、菜单图标、下拉箭头、avatar 占位、
<TrendCharts />等)补aria-hidden="true" - 新增
:focus-visible样式,确保键盘焦点可见(outline: 2px solid var(--el-color-primary)) <el-avatar>补:alt="user?.username || ''"
- 表单
<el-form>补:aria-label="t('auth.login')"与@submit.prevent="handleLogin"(支持回车提交) - 用户名 / 密码
<el-input>补:aria-label(屏幕阅读器需要 — placeholder 不替代 label) - 输入框补
autocomplete="username"/autocomplete="current-password"(密码管理器 + a11y 双赢) - 登录按钮补
native-type="submit"+:aria-label - 注册链接补
:aria-label
- 新建会话圆形按钮(icon-only)补
aria-label="新建会话",icon 标aria-hidden="true" - 会话搜索框补
aria-label="搜索会话标题" - 主对话输入区
<el-input type="textarea">补:aria-label="inputPlaceholder" - 模型选择
<el-select>补aria-label="选择 AI 模型"
- 4 张统计卡的装饰性
<el-icon>补aria-hidden="true" - 3 张 Quick Start 卡片:
<div @click>→<div role="button" tabindex="0">+ 键盘事件(Enter / Space),并补:aria-label与:focus-visible样式
- toolbar 按钮补
:aria-label与 iconaria-hidden="true" - 视图切换 radio-group 补
:aria-label,每个 radio-button 补:aria-label(区分 card / table) - 全量国际化(30+ zh-CN literals →
workspace.*命名空间)
- 加载状态补
role="status"+aria-label - 错误状态补
role="alert" - 操作按钮补
:aria-label,icon 补aria-hidden="true" - 关键 selector:补
data-test="backtest-detail"与data-test="backtest-status"(smoke test 依赖)+data-test="equity-curve"(同) - 状态文本通过
<span class="sr-only">注入,对屏幕阅读器可见但视觉不显示
- 创建策略按钮补
:aria-label,iconaria-hidden="true" - 搜索框补
aria-label="搜索策略" - 类别 radio-group 补
aria-label="按策略类别过滤" - 策略卡
<el-card>:补role="button" tabindex="0"+@keydown.enter/@keydown.space.prevent+:aria-label="\策略 ${t.name}`"` - 新增
.strategy-card:focus-visible样式
- 文档列表
<section>补:aria-label="'参考文档'" - 每个 citation
<button>补data-test="citation-chip"(smoke test 依赖)+:aria-label(含序号与标题) - icon 补
aria-hidden="true"
- 知识库搜索
<input>补aria-label - 其余
<button>已是真实按钮元素,文本可见,a11y 基线达标
- 新增
.sr-only全局工具类:标准实现(position absolute / clip rect / 1px / -1px margin),用于「屏幕阅读器可见、视觉不显示」的内容
上述修复后,仍需在首次
frontend-a11yCI red line 后由团队继续清理BacktestPage/ 剩余 KnowledgeBasePage 子组件的具体违规。175 验收基线为「框架就绪 + 7 页核心结构清理」。
对 7 页 Critical_Page_Set 做了静态 a11y 审计(缺 alt / icon-only button 无
aria-label / 表单控件无关联 label / 装饰图标无 aria-hidden 四类硬约束):
- LoginPage / DashboardPage / AIChatPage / StrategyPage / BacktestResultPage:
175 已清理,复审无新增 always-visible 违规(icon-only 按钮均带
aria-label, 装饰<el-icon>均带aria-hidden,表单输入均有 label/aria-label)。 - BacktestPage:全部输入位于
<el-form-item :label>内、按钮均有可见文本, 无违规。 - KnowledgeBasePage:修复了实际违规——
- 文档搜索
<input>仅有placeholder→ 补aria-label="搜索文档"(placeholder 不替代 label,axelabelserious) - 排序
<select>无可访问名 → 补aria-label="文档排序方式"(axeselect-name) - 每页数量
<select>无可访问名 → 补aria-label="每页显示数量" - 树视图 + 表格视图的逐行选择
<input type=checkbox>无可访问名 → 补:aria-label="选择文档 {title}" - 8 个对话框的
✕关闭按钮(自定义非 el-dialog)→ 补aria-label="关闭对话框" - 新增 i18n key:
kb.searchDocs / sortDocsBy / selectDocAria / perPageAria / closeDialog(zh-CN + en-US 同步)
- 文档搜索
注:
critical/serious的「0 违规」终态判定仍需真实浏览器跑@axe-core/playwright的frontend-a11yjob(本地无头浏览器环境不可用)。本轮做的是静态源码审计 + 修复发现的确定性违规;CI a11y job 仍是最终裁判(已是 PR-blocking)。 对比度(约束 c)与焦点可达(约束 d)属运行时/视觉判定,依赖 axe + Lighthouse ≥0.9 门禁,不在静态审计范围内。
cd src/frontend
npm ci
npm run build
# In one terminal: serve the built dist (or use vite preview)
npx vite preview --port 4173 &
# In another terminal: run a11y suite
BASE_URL=http://localhost:4173 npx playwright test e2e/a11y/.github/workflows/ci.ymlfrontend-a11yjob — required, blocks PR.config/lighthouserc.jslighthouse-cijob — required, blocks PR.- Failed scans upload Playwright trace + axe violation list as artifact (7-day retention).