vanilla-extract
在 TypeScript 中编写类型安全样式,并在构建时输出静态 CSS。
项目概述
vanilla-extract 提供接近原生 CSS 的 TypeScript API、局部作用域类名、类型安全变量与主题,并通过构建工具集成生成零样式运行时的 CSS 文件。
vanilla-extract 是采用 MIT 许可证的零样式运行时 CSS-in-TypeScript 工具。开发者在 .css.ts 文件中调用 style、createTheme、createVar 等 API,使用普通 TypeScript 数据结构描述 CSS;构建插件执行这些模块,生成局部作用域类名、CSS Variables、Keyframes、Font Face 和静态 CSS 文件,而样式定义代码不会进入浏览器 JavaScript Bundle。它保持对标准 CSS 的轻量抽象,同时通过 CSSType 提供属性和值的类型检查。核心包之外,Sprinkles 可以生成项目专属的原子 Utility,Recipes 提供类型安全的组件 Variant,Dynamic 则仅在确实需要运行时值时更新预先声明的 CSS Variables。
主要特点
vanilla-extract 把 TypeScript 的类型、模块和组合能力用于样式作者体验,同时让浏览器最终加载普通静态 CSS。
构建时生成静态 CSS
.css.ts 模块在构建过程中执行并输出普通 CSS,样式声明本身不会进入最终浏览器 JavaScript。
类型安全的标准 CSS
样式对象通过 CSSType 检查 CSS 属性和值,并为伪类、媒体查询、Supports、Container Query 和 Cascade Layer 提供结构化语法。
局部作用域类名
style 返回构建时生成的类名,默认避免组件间命名冲突,并可像 CSS Modules 类名一样直接传给 className。
主题契约与 CSS Variables
createThemeContract 定义变量结构,createTheme 完整实现契约;缺少或写错 Token 会在 TypeScript 阶段暴露。
静态样式组合
style 可组合类名和样式对象,复用基础规则;生成结果仍是普通 Class List,并保留可用于选择器的唯一标识。
Recipes 组件变体
可选 Recipes 包支持 Base、Variant、Compound Variant 和默认值,生成静态样式并导出类型安全的轻量选择函数。
Sprinkles 原子 Utility
可选 Sprinkles 包从团队允许的属性、Token 与响应式条件生成原子类,形成项目专属、零样式运行时的 Utility API。
多构建工具集成
官方提供 Vite、esbuild、Webpack、Next.js、Parcel、Rollup 和 Gatsby 等集成,可让生成 CSS 参与原有拆包与缓存流程。
适用场景
适合需要类型安全主题、组件变体、框架无关样式包或零样式运行时输出的应用和设计系统。
TypeScript 设计系统
将颜色、间距、字体、圆角和组件 Variant 建模为可导入、可检查、可版本化的 TypeScript API。
React 与多框架应用
生成结果只是类名和 CSS,因此可用于 React、Vue、Svelte、Solid 或原生 DOM,前提是目标工具链配置了集成插件。
共享组件库
组件样式、主题契约和类名可以随包发布,让多个应用复用同一设计语言与类型定义。
多品牌与多主题产品
不同主题可完整实现同一契约,并按容器应用对应主题类,适合深浅模式、品牌皮肤和局部组件主题。
性能敏感界面
样式在构建时生成,不需要浏览器端 CSS-in-JS 引擎进行解析、哈希和动态 Style Tag 注入。
受控 Utility 与组件 Variant
Sprinkles 和 Recipes 适合把允许的响应式属性、Token 与组件状态封装成类型安全 API。
优点与注意事项
技术选型不仅要看能力,也要理解它带来的团队成本。
主要优点
vanilla-extract 擅长的地方
- 样式定义在构建时转为静态 CSS,不承担常规 CSS-in-JS 的样式生成运行时
- CSS 属性、主题契约、Token 和 Recipe Variant 都能获得 TypeScript 检查与自动补全
- 核心 API 接近标准 CSS,媒体查询、选择器、变量和 Layer 等能力仍可直接表达
- 局部作用域类名减少命名冲突,样式又能通过普通模块导入和组合
- 主题契约可确保多个品牌和颜色模式实现相同的 Token 结构
- 核心、Sprinkles、Recipes 与 Dynamic 分层,可按项目需求选择抽象程度和运行时成本
需要注意
采用前应考虑的问题
仅安装 @vanilla-extract/css 无法处理 .css.ts 文件;Vite、Webpack、Next.js 等环境都要安装并启用对应官方集成。
style、createTheme 等构建时 API 应放在 .css.ts 文件,不应在 React 渲染函数或普通运行时代码中动态调用。
用户输入、数据库颜色或实时尺寸应使用预生成 Variant、CSS Variable 或 Dynamic 包,而不是运行时创建新的样式对象。
严格契约有利于治理,但新增 Token 会迫使所有主题同步更新;大型多品牌项目需要规划升级顺序和包版本。
组件通常需要同时维护 .tsx 和 .css.ts 模块,喜欢单文件模板或内联样式的团队可能觉得文件切换较多。
Sprinkles、Recipes、Theme Contract 和 Dynamic 解决不同问题,若没有清晰边界,团队可能同时维护多套重叠的样式 API。
共享包应明确是否发布已生成 CSS、如何处理 External 和文件扩展,并在各消费框架中验证 SSR、拆包和样式顺序。
核心静态样式不需要样式运行时,但 Recipes 的 Variant 选择、Dynamic 的变量赋值以及应用自身的类名逻辑仍会产生少量 JavaScript。
快速开始
使用官方 Vite 插件配置 React + TypeScript 项目,再创建主题、静态样式和类型安全 Recipe。
bashnpm create vite@latest vanilla-app -- --template react-ts
cd vanilla-app
npm install
npm install @vanilla-extract/css @vanilla-extract/recipes
npm install -D @vanilla-extract/vite-plugintypescriptimport { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
export default defineConfig({
plugins: [react(), vanillaExtractPlugin()],
});typescriptimport { createTheme } from "@vanilla-extract/css";
export const [lightTheme, vars] = createTheme({
color: {
brand: "#4b67ff",
surface: "#ffffff",
text: "#0f172a",
},
space: {
small: "0.75rem",
medium: "1.25rem",
},
});
export const darkTheme = createTheme(vars, {
color: {
brand: "#8b7cff",
surface: "#0f172a",
text: "#f8fafc",
},
space: {
small: "0.75rem",
medium: "1.25rem",
},
});typescriptimport { style } from "@vanilla-extract/css";
import { vars } from "./theme.css";
export const page = style({
minHeight: "100vh",
display: "grid",
placeItems: "center",
padding: vars.space.medium,
background: vars.color.surface,
color: vars.color.text,
});
export const card = style({
width: "min(32rem, 100%)",
padding: vars.space.medium,
borderRadius: "1rem",
boxShadow: "0 20px 60px rgb(15 23 42 / 0.12)",
"@media": {
"screen and (min-width: 768px)": {
padding: "2rem",
},
},
});typescriptimport { recipe } from "@vanilla-extract/recipes";
import { vars } from "./theme.css";
export const button = recipe({
base: {
border: 0,
borderRadius: "0.75rem",
fontWeight: 700,
cursor: "pointer",
},
variants: {
tone: {
brand: {
background: vars.color.brand,
color: "white",
},
neutral: {
background: vars.color.surface,
color: vars.color.text,
},
},
size: {
small: { padding: "0.5rem 0.75rem" },
medium: { padding: "0.75rem 1rem" },
},
},
defaultVariants: {
tone: "brand",
size: "medium",
},
});tsximport { lightTheme } from "./theme.css";
import { page, card } from "./app.css";
import { button } from "./button.css";
export function App() {
return (
<main className={`${lightTheme} ${page}`}>
<section className={card}>
<h1>vanilla-extract</h1>
<p>TypeScript 作者体验,静态 CSS 输出。</p>
<button className={button({ tone: "brand" })}>
开始使用
</button>
</section>
</main>
);
}bashnpm run dev
# 提交前验证
npm run build
npm run preview下一步:所有静态样式必须放在 .css.ts 文件并由官方构建插件处理。不要在组件渲染期间调用核心样式 API;运行时数据应映射到预生成 Variant,或通过 @vanilla-extract/dynamic 写入已声明的 CSS Variable。
类似项目
这些方案同样把组件化样式转换为静态 CSS,但对原子化、主题、变体、静态分析和框架集成的取舍不同。
vanilla-extract vs StyleX
vanilla-extract 和 StyleX 都使用类型安全对象描述样式,并在构建阶段输出静态 CSS。vanilla-extract 更接近 CSS Modules-in-TypeScript,默认保留每个 style 对应的局部类;StyleX 则系统性地将声明编译为可全局复用的原子 CSS,并强调确定性的跨文件组合。
| 比较维度 | vanilla-extract | StyleX |
|---|---|---|
| 核心语法 | .css.ts 中的 style、createTheme 与普通类名导入 | stylex.create 定义样式,stylex.props 负责组合和应用 |
| CSS 输出 | 局部作用域规则,可选 Sprinkles 生成原子 Utility | 默认把静态声明编译为原子 CSS |
| 组合方式 | 组合类名或样式数组,组件接收普通 className | stylex.props 处理冲突,并保证后传入样式获胜 |
| 主题 | createThemeContract、createTheme 与 scoped variables | defineVars、createTheme 与类型化 CSS Variable |
| 组件变体 | 可选 Recipes 提供 Variant 和 Compound Variant | 主要使用 JavaScript 条件组合预定义样式 |
| Utility 能力 | Sprinkles 可生成项目专属、类型安全的原子 API | 核心输出本身原子化,但不提供 Tailwind 式 Utility DSL |
| 框架范围 | 生成类名可供多种框架和原生 DOM 使用 | 理念可跨框架,目前 React 集成和用法最成熟 |
| 更适合 | 框架无关组件库、类型安全主题和接近标准 CSS 的团队 | 大型 React 产品、确定性组合和跨包原子规则复用 |
如果团队希望用 TypeScript 作为 CSS 预处理器,保留普通 className、局部作用域规则和多框架适用性,同时按需引入 Recipes 或 Sprinkles,vanilla-extract 更自然;如果核心场景是大型 React 代码库,需要内建原子化和严格可预测的跨文件样式覆盖,StyleX 更聚焦。两者都依赖构建插件,选择前应使用真实 SSR、组件库发布和主题切换流程验证集成。
资料核验
版本、维护信息与本页采用的官方资料来源。
本页依据 vanilla-extract 官方入门、Vite 集成、Styling、Theming、Recipes、Sprinkles 与 Dynamic 文档,以及官方源码仓库整理。版本通过 npm 官方 Registry 核验;快速开始使用当前官方 @vanilla-extract/vite-plugin 和 .css.ts 工作流。
官方仓库未归档,核验时最近可见的代码活动日期为 2026 年 7 月 27 日。该状态表示项目近期仍有公开维护活动,不代表固定发布频率或长期支持承诺。