vanilla extract 的类型安全需依赖 @vanilla-extract/css 与 typescript 联合推导,通过 stylevariants、defineproperties、createthemecontract 等 api 手动构建;style 返回 string 无类型校验,易拼错且重构不报错;stylevariants 对枚举变体提供联合类型约束;defineproperties 配合 as const 锁定设计 token 键名;createthemecontract 以契约先行保障主题类型完整性。

Vanilla Extract 本身不提供类型安全,它只生成 string 类名;真正的类型安全必须靠 @vanilla-extract/css + TypeScript 的联合推导能力,配合 styleVariants、defineProperties、createThemeContract 等 API 手动构建。
为什么 style 返回的类名不是类型安全的?
style 函数返回的是 string,TypeScript 完全无法校验你后续是否拼错类名、是否真实存在该样式。比如:
const button = style({ color: 'red' });
div.className = 'btn-clas-nam'; // ✅ 编译通过,但运行时无效
- 这种写法和手写字符串无异,失去所有类型保护
- 组件中引用
button时,它的类型就是string,没有键名约束、没有变体枚举、没有设计 token 键校验 - 一旦重构类名或删除样式,TS 不会报错,只能靠运行时发现
用 styleVariants 给有限状态加联合类型约束
适合按钮变体、尺寸、禁用态等明确枚举的场景,让 TypeScript 推导出 Record 这样的精确返回类型:
const variants = styleVariants({
primary: { backgroundColor: 'blue' },
secondary: { backgroundColor: 'gray' }
});
- 键名必须显式写出,漏一个或拼错(如
'primery')就会触发 TS 错误 - 使用时必须传入字面量类型:
button.className = variants[props.variant]—— 如果props.variant是string,会直接报错 - 禁止在
styleVariants内部用动态 key:[dynamicKey]: {},TS 无法推导类型
用 defineProperties + styleMap 控制设计 Token 键名类型
当你有一套固定的设计系统(如 spacing、colors、radii),defineProperties 能把它们声明为 const 对象,让 TypeScript 精确推导键名类型;styleMap 则基于这些属性生成可索引的样式映射:
const vars = defineProperties({
properties: {
color: {
primary: '#0070f3',
secondary: '#666'
}
}
});
export const colorStyles = styleMap(vars.color);
-
vars.color的类型是{ primary: string; secondary: string },键名被严格锁定 -
colorStyles.primary和colorStyles.secondary都是string,但访问非法键(如colorStyles.accent)会立即报错 -
defineProperties参数必须带as const断言,否则 TS 会宽泛推导为Record<string unknown></string>
createThemeContract 是主题类型安全的唯一可靠方式
手动标注主题变量类型容易遗漏或不一致;createThemeContract 强制你先声明契约结构,再由 createTheme 去实现,任何缺失或类型不匹配都会在编译期暴露:
const themeContract = createThemeContract({
color: {
background: null,
text: null
}
});
export const darkTheme = createTheme(themeContract, {
color: {
background: '#1a1a1a',
text: '#ffffff'
}
});
-
themeContract是纯类型定义,不生成 CSS,只用于约束实现 -
createTheme第二个参数必须完全满足契约,少一个字段、多一个字段、类型不对(比如background写成 number)都会报错 - 动态计算值(如
background: lighten('#1a1a1a', 0.1))会丢失类型,因为 TS 无法静态分析函数返回值
真正难的不是写对一个 style,而是让整个样式系统的每个出口——变体、token、主题——都保持可索引、不可拼错、删了就报错。这需要主动放弃“写完就跑”的惯性,用 as const、契约先行、显式枚举来换类型保障。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











