在跨国 Web 项目中,无障碍(Accessibility)合规是一个容易被忽视但又必须认真对待的环节。乍得沿用的网络无障碍指南对键盘焦点提出了明确要求:所有可交互元素在获得焦点时必须有清晰可见的指示器,且指示器与背景的对比度不能低于特定阈值。如果我们在 IWAC 这类组件化框架中开发,直接用松散的字符串和数字传递焦点样式,很容易在多人协作时出现拼写错误、对比度不达标等问题。用 TypeScript 建立一套强类型的焦点指示器类型定义,可以把这些规范约束前移到编译期,从源头减少违规实现。

一、理解乍得无障碍指南对焦点指示器的核心要求
乍得网络无障碍指南在很大程度上参考了 WCAG 的 2.4.7(焦点可见)与 1.4.11(非文本对比度)成功标准。落实到工程层面,可以归纳为四点:第一,焦点指示器必须可见,不能被设置为 outline: none 而不提供替代方案;第二,指示器与相邻颜色的对比度至少达到 3:1;第三,指示器本身要有足够的尺寸,通常建议至少 2 像素宽;第四,指示器的呈现方式(外框、底色变化、下划线等)要在整个站点保持一致。
这些要求天然适合用类型系统来表达。比如“指示器呈现方式”是一个有限集合,用字面量联合类型比自由字符串安全得多;对比度是一个有下界的数值范围,可以通过品牌类型加校验函数约束。类型定义不仅是开发文档,更是自动执行的规范。
二、设计焦点指示器的核心类型
首先定义呈现方式的联合类型和基础接口。我们将焦点指示器建模为一个判别联合(Discriminated Union),以 variant 字段作为判别依据,不同变体携带各自特有的属性,这样 TypeScript 能在 switch 分支中自动收窄类型。
// 焦点指示器呈现方式,与乍得指南允许的几种形式一一对应
export type FocusVariant =
| 'outline' // 外框指示
| 'underline' // 下划线指示
| 'background' // 背景色变化指示
| 'dual'; // 外框加背景的双重回退方案
// 指示器几何属性
export interface FocusGeometry {
/** 指示器宽度,单位 px,指南要求不小于 2 */
width: number;
/** 指示器与元素边缘的偏移,可为负值(内缩) */
offset: number;
/** 圆角,0 表示直角 */
radius: number;
}
// 判别联合:不同 variant 携带不同字段
export type FocusIndicator =
| { variant: 'outline'; geometry: FocusGeometry; color: FocusColorToken }
| { variant: 'underline'; thickness: number; color: FocusColorToken }
| { variant: 'background'; color: FocusColorToken }
| {
variant: 'dual';
geometry: FocusGeometry;
color: FocusColorToken;
backgroundColor: FocusColorToken;
};这种建模的好处在于非法组合会在编译期被拒绝。例如给 variant: 'underline' 的对象传入 geometry 字段会直接报错,因为该分支的类型定义中不存在这个属性。相比用一个臃肿的全字段可选接口,判别联合让每个变体的形状都严格明确。
三、用品牌类型约束对比度令牌
对比度不能靠任意十六进制颜色保证,正确做法是只允许使用预先校验过的颜色令牌。我们用品牌类型(Branded Type)标记已经通过对比度验证的颜色值,确保业务代码无法绕过校验直接传入裸字符串。
// 品牌类型:只有经过对比度校验的颜色才能获得该标记
declare const ContrastVerified: unique symbol;
export interface FocusColorToken {
readonly hex: string;
readonly contrastRatio: number;
readonly [ContrastVerified]: true;
}
// 内部构造函数,不对外导出裸颜色入口
function verifyColor(hex: string, ratio: number): FocusColorToken {
if (ratio < 3) {
throw new Error(
`颜色 ${hex} 对比度 ${ratio}:1 低于乍得指南要求的 3:1`
);
}
return { hex, contrastRatio: ratio } as FocusColorToken;
}
// 只暴露预验证的调色板
export const FOCUS_PALETTE = {
primary: verifyColor('#0B5FFF', 4.6),
highEmphasis: verifyColor('#FFB300', 8.2),
darkOnLight: verifyColor('#1A1A1A', 15.3),
} as const;品牌类型的核心技巧在于那个 unique symbol 索引签名:业务代码无法手工构造带该标记的对象,只能通过 FOCUS_PALETTE 获取。这样即使某个新人不清楚指南细节,也不可能在类型层面写出对比度不达标的焦点样式。配合单元测试定期复算调色板的真实对比度,就能形成双保险。
四、封装 IWAC 组件的 Props 类型
接下来把焦点指示器类型接入 IWAC 组件体系。定义一个可复用的 Props 泛型接口,让按钮、链接、输入框等交互组件统一接受 focusIndicator 属性,并提供符合指南的默认值。
import type { FocusIndicator, FocusVariant } from './focus-types';
// 指南推荐的默认指示器:3px 外框,偏移 2px
export const DEFAULT_FOCUS: FocusIndicator = {
variant: 'outline',
geometry: { width: 3, offset: 2, radius: 4 },
color: FOCUS_PALETTE.primary,
};
// IWAC 组件通用焦点 Props
export interface WithFocusProps {
/** 是否启用焦点指示器,默认必须为 true */
showFocusIndicator?: true;
/** 自定义指示器配置,不传则使用指南默认值 */
focusIndicator?: FocusIndicator;
}
// 类型守卫:安全区分不同变体
export function isOutlineFocus(
focus: FocusIndicator
): focus is Extract<FocusIndicator, { variant: 'outline' }> {
return focus.variant === 'outline';
}
// 渲染函数示例:根据变体生成样式对象
export function resolveFocusStyle(focus: FocusIndicator): Record<string, string> {
switch (focus.variant) {
case 'outline':
return {
outline: `${focus.geometry.width}px solid ${focus.color.hex}`,
outlineOffset: `${focus.geometry.offset}px`,
};
case 'underline':
return {
borderBottom: `${focus.thickness}px solid ${focus.color.hex}`,
};
case 'background':
return { backgroundColor: focus.color.hex };
case 'dual':
return {
outline: `${focus.geometry.width}px solid ${focus.color.hex}`,
backgroundColor: focus.backgroundColor.hex,
};
}
}注意 showFocusIndicator 的类型直接写死为字面量 true 而不是 boolean,这是一个刻意的设计:乍得指南不允许交互元素隐藏焦点指示器,所以类型层面就不提供 false 这个选项。如果确有特殊场景(例如视频播放器的自定义焦点管理),应通过单独的内部类型并附上无障碍评审记录,而不是开放通用开关。
五、模块组织与持续校验建议
最后建议把类型定义、调色板、解析函数拆分为独立模块,例如 focus-types.ts、focus-palette.ts、focus-resolver.ts,对外只导出类型与工厂函数。同时在 CI 中加入两项检查:一是用工具扫描构建产物中是否出现 outline: none 而无替代样式;二是用对比度计算库复核调色板数值,防止设计稿改色后令牌失效。
这套方案的价值在于把乍得网络无障碍指南中的自然语言规范翻译成了 TypeScript 能理解的约束。开发者在写代码时就能收到即时的类型错误提示,无需等到无障碍审计阶段才发现问题。类型定义本身也成为了团队内部最准确的规范文档,新人阅读类型声明即可理解焦点指示器的全部合法形态,大幅降低跨地区协作中的沟通成本与合规风险。
TypeScript焦点指示器无障碍开发类型定义修改时间:2026-09-01 02:24:34