Ship Accessible Charts 是一份发布前工作流程:触发 → 检查 → 决策 → 构建 → 验证。只有当图表的语义替代表示、适配器生命周期、SSR 策略、渲染器、导出、包边界和销毁流程都在贴近真实消费者的场景中得到验证,图表才算真正“交付”。
无障碍检查
检查:名称要能标识被比较的对象
期望示例:
const hostOptions = {
definition,
height: 320,
initialWidth: 640,
ariaLabel: 'Weekly downloads for Core and React packages',
ariaDescription: 'Values are seven-day totals. Missing weeks appear as gaps.',
}
失败条件:标签只写“chart”,或重复可见标题却没有指标,或者遗漏比较对象与时间范围。
修复:写明指标、被比较的实体和时间范围;把结论与详细数值放在常规应用内容中。
检查:关键数值要有等价表示
期望:可见的标题与单位,当精确数值或应用决策依赖图表时,还要在邻近位置提供摘要或语义化表格。
失败条件:获取必要数值的唯一方式是指针悬停、颜色、动效或视觉估算。
修复:把相同的语义行或选定键绑定到应用文本/表格控件。
检查:每个交互都有非指针路径
期望:原生点键盘导航、用于自由光标和范围控件的语义化按钮/输入、可见焦点、取消/重置路径,以及有意义的已提交状态文本。
失败条件:透明指针覆盖层是唯一的控制面。
修复:在键盘契约匹配的地方使用一方控件,否则使用应用自有的语义化控件。
生命周期检查
检查:SSR 使用受支持的适配器和确定性输入
期望示例:
import {
createChartRuntime,
defineChart,
lineY,
renderChartSvg,
} from '@tanstack/charts'
import { scaleLinear } from '@tanstack/charts/scales/linear'
import { scaleUtc } from 'd3-scale'
interface TrafficRow {
date: Date
visits: number
}
const rows: readonly TrafficRow[] = [
{ date: new Date('2026-08-10T00:00:00Z'), visits: 820 },
{ date: new Date('2026-08-11T00:00:00Z'), visits: 910 },
]
const definition = defineChart({
marks: [lineY(rows, { x: 'date', y: 'visits' })],
scales: {
x: { scale: scaleUtc },
y: { scale: scaleLinear },
},
})
const runtime = createChartRuntime<TrafficRow, Date, number>()
const scene = runtime.render(definition, { width: 720, height: 400 })
export const svg = renderChartSvg(scene, {
ariaLabel: 'Daily traffic',
idPrefix: 'daily-traffic',
})
runtime.destroy()
失败条件:definition 的构建读取了浏览器布局、随机值、本地时间或未解析的异步数据;或者所选的适配器不承诺支持 SSR。
修复:在渲染前解析数据,使用确定性的格式化和尺寸,并遵循所选适配器的参考文档。
检查:挂载、更新和清理保持由适配器负责
期望:每个已挂载的适配器实例对应一个 runtime;完整的不可变选项更新;在捕获值变化前定义保持稳定;卸载/断开时执行清理。
失败条件:应用代码在服务端渲染期间调用 DOM 测量或挂载浏览器宿主,或者在移除后仍保留观察器/监听器。
修复:使用适配器生命周期,或仅在浏览器拥有的挂载阶段调用 mountChart,并在拆除时调用 destroy()。
渲染器与导出检查
检查:渲染器选择与任务匹配
SVG:服务端可见几何、矢量导出、DOM 样式、常规交互图表。
Canvas:高场景数量或栅格优先输出;服务端输出无障碍外壳,而非像素。
静态 SVG:确定性服务端/导出产物,不带浏览器交互。
React Native SVG:明确的原生目标,需在设备级别验证。
检查:导出可复现
期望:明确的尺寸、主题/背景、作用域 ID、可移植的字体/资源、有意包含焦点,以及有意义的导出名称/描述。
失败条件:导出依赖当前响应式像素或无法访问的应用 CSS/资源。
修复:渲染明确的 scene,或按明确的产物策略进行序列化/栅格化。
常见错误
CRITICAL 使用通用的无障碍标签
错误:ariaLabel: 'Chart'。
正确:写明理解该图形所需的指标、实体、时间范围与单位。
通用标签只暴露一个可聚焦的图形,却没有有用的身份。
来源:docs/guides/accessibility.md
HIGH 让图表成为唯一表示
错误:要求通过悬停或视觉估算来获取精确的操作数值。
正确:从相同的语义数据/状态渲染摘要、表格或应用控件。
当精确数值至关重要时,图表表面只是补充。
来源:docs/guides/accessibility.md;归档的 responsive/accessibility 笔记
CRITICAL 在服务端渲染期间挂载浏览器宿主
错误:从服务端生命周期调用 mountChart 或适配器 DOM mount。
正确:通过受支持的 SSR 适配器或 renderChartSvg 预渲染确定性 scene,然后仅在真实元素存在后再挂载浏览器宿主。
测量和变更需要浏览器元素。较早的 Angular 变通方案尤其容易出现这种边界错误。
来源:GitHub issue 56;docs/guides/ssr-and-hydration.md
HIGH 把行为放在适配器 props 上
错误:
<Chart definition={chart} tooltip keyboard />
正确:
const interactive = defineChart(chart, { tooltip, keyboard: true })
<Chart definition={interactive} ariaLabel="Revenue by month" />
焦点、tooltip、键盘、控件、光标和 SVG 动画属于可复用的 definition;适配器负责表面生命周期和框架主体组合。当前的 definition API 取代了遗留的适配器行为 props。
来源:CHANGELOG.md 中的 chart-behavior 迁移;React chart 参考
HIGH 在原生环境中导入通用 barrel
错误:为每个原生图表使用 @tanstack/charts/universal。
正确:使用精确的 core mark/scale/scene 以及 @tanstack/charts/react-native 子路径,除非跨平台编写方式证明使用通用包是合理的。
精确导入可以保护 Metro 和原生声明环境,使其免受无关的浏览器和可选能力代码影响。
来源:API-FRICTION.md F-154、F-171、F-173、F-256;React Native 包 README
HIGH 张力:丰富交互与可移植渲染
DOM 便利性可能破坏静态 SVG、Canvas、原生、SSR 或键盘等价性。将语义保留在 definitions 和应用状态中;仅在呈现接缝处使用平台特定的 body/host 扩展。
另见:build-chart-interactions/SKILL.md 和 extend-tanstack-charts/SKILL.md
部署前摘要
- 有意义的名称和简洁的描述。
- 可见的单位、时间范围和来源上下文。
- 在需要时提供精确数值摘要/表格。
- 指针、键盘、触摸和取消/重置场景。
- 减少动效和非颜色证据。
- 受支持的 SSR 策略与确定性初始几何。
- 稳定的 definition、mark、datum 和资源 ID。
- 已验证挂载、更新、调整大小、字体重排和销毁。
- 从打包后的消费者验证渲染器和导出行为。
- 在适用时,在目标设备上验证原生声明。
参考资料
React
Preact
Vue
Solid
Svelte
Angular
Lit
Alpine
Octane
React Native
渲染器与导出
另见:debug-and-verify-charts/SKILL.md —— SSR、无障碍、渲染器和包声明需要消费者级别的证据。