关于 tailwind-to-stylex
一项将 Tailwind 工具类迁移到 StyleX、同时保留渲染样式的技能。它指导开发者解析 CSS、处理条件与结构选择器、使用适合框架的 API,并验证迁移结果。
主要功能
- 01将 Tailwind 类解析为 CSS
- 02将条件映射到 StyleX 值
- 03处理需要调整标记结构的选择器
- 04按框架应用 props 或 attrs
- 05报告未转换项并验证迁移
适用场景
- 01将组件从 Tailwind 迁移到 StyleX
- 02转换响应式与状态变体
- 03找出需要重构的工具类
- 04对迁移文件执行类型检查和 lint
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ shadcn-labs/ skills --skill tailwind-to-stylex安装后在 Claude Code 中输入/tailwind-to-stylex确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
SKILL.zh-CN.mdevals/evals.jsonevals/files/button/package.jsonevals/files/button/src/Button.tsxSpinner.tsxevals/files/button/src/lib/utils.tsevals/files/navbar/package.jsontailwind.config.tsevals/files/navbar/src/Navbar.tsxevals/files/price-card/package.jsonvite.config.tsevals/files/price-card/src/PriceCard.tsxreferences/mapping.mdstylex-rules.md详细介绍
将 Tailwind 迁移到 StyleX
将 Tailwind 工具类转换为 StyleX 样式。把 className="..." 字符串
替换为 stylex.create({...}) 调用中的条目,并用 stylex.props(...) 应用它们。
保持渲染输出不变。迁移过程中不要重新设计组件。如果某个 样式无法精确转换,请在步骤 6 中标记出来,而不是悄悄改变 设计。
核心机制:先解析,再重塑
像 tw-to-stylex codemod 一样转换类字符串。将每个类解析为
它生成的 CSS,然后将该 CSS 重塑为 StyleX 对象。不要根据类
名进行猜测。计算出的 CSS 才是必须保持完全一致的行为。
"px-4 py-2 text-sm font-medium hover:bg-blue-600"
│
▼ resolve to CSS using the project's Tailwind version and config
padding-inline: 1rem; padding-block: .5rem;
font-size: .875rem; line-height: 1.25rem; font-weight: 500;
:hover { background-color: #2563eb }
│
▼ reshape to StyleX with camelCase properties and conditional values
{
paddingInline: '1rem',
paddingBlock: '0.5rem',
fontSize: '0.875rem',
lineHeight: '1.25rem',
fontWeight: 500,
backgroundColor: { default: null, ':hover': '#2563eb' },
}
如果某个类使用了任意值(例如 w-[37px])、项目的自定义主题或插件,
解析为 CSS 仍然可行,因为该值来自类或项目配置。
当自定义配置或不熟悉的插件导致输出不明确时,请查看
项目的 tailwind.config / CSS @theme,或者解析它,而不是猜测。
两个容易让转换出错的地方
条件位于属性值中
Tailwind 将状态和响应式变体(例如 hover:、md: 和 dark:)分散
在单独的规则中。StyleX 则把
条件嵌套在属性值内部。只要某个属性
带有任何条件,就必须有 default 键。没有基础值时使用 null。如果没有 default,StyleX 可能会
忽略该条件。
// md:flex hover:opacity-80 -> conditions live on each property
display: { default: 'block', '@media (min-width: 768px)': 'flex' },
opacity: { default: 1, ':hover': 0.8 },
有些选择器需要结构上的改动
space-x-*、divide-*、group/group-hover:*、peer/peer-*:* 以及排版相关的
prose 都依赖于后代、兄弟或祖先状态选择器。StyleX 只为
单个元素设置样式,因此这些无法直接映射。请重构它们,并告知用户。例如,
在 flex 父元素上用 gap 替换 space-x,或者将 group-hover 状态提升到
React 或 CSS 变量中。每种情况请参阅 references/mapping.md。
工作流
一次处理一个组件文件,这样每次改动都保持可审查。
步骤 1:确认 StyleX 已设置
StyleX 需要 @stylexjs/stylex,以及用于 Babel、Vite、Next.js、Webpack
或 rspack 的编译器插件。没有该插件,stylex.props 在运行时会返回空
,迁移看起来就是坏的。检查 package.json 和打包器配置。如果尚未
设置,请阅读 references/stylex-rules.md 中的项目设置部分。在用户要求设置时
进行配置,或者在转换前说明缺少什么。还要识别 React DOM 和 react-strict-dom,因为它们应用样式的方式不同。
步骤 2:找到 Tailwind 的使用位置
定位文件中的每个 className 和 class,包括对 cn(...)、
clsx(...)、twMerge(...) 的动态调用、模板字面量和三元表达式。你需要可能应用到每个元素的类的完整
集合,以及在哪些条件下应用,才能忠实地进行转换。
步骤 3:构建 stylex.create 对象
为每个不同的元素或变体创建一个命名条目,例如 base、label 或
iconActive。使用描述性名称代替 $1 和 $2。将每个条目的类解析为
CSS,然后应用以下 StyleX 规则:
- 每个属性都使用 camelCase。例如,
border-radius变为borderRadius。 - 使用长写属性或单值简写。StyleX 会对多值简写发出警告,
因为它们会导致合并冲突。将
border: '1px solid red'拆分为borderWidth: 1, borderStyle: 'solid', borderColor: 'red'。将双值的padding拆分为paddingBlock/paddingInline。像padding: 16这样的单值简写是有效的。 - 对于长度属性,数字表示 px。
width: 24表示 24px。其他单位保留为 字符串,例如'1.5rem'、'50%'和'100vh'。 - 条件应放在属性值中,并且需要
default。在移动优先顺序中, 无前缀的类是default;断点变为带有min-width的@media键。 - 用于状态、深色模式、结构选择器、任意
值、渐变和动画的修饰符到条件映射表位于
references/mapping.md。每当你遇到 超出普通工具类范围的内容时,请阅读它。
步骤 4:应用样式
前三个步骤对每个框架都是相同的。只有应用器会变化:
- 在 React DOM、Preact、react-strict-dom 和其他 JSX 展开框架中,展开
stylex.props(...),它返回{ className, style }。 - 在 Solid、Svelte、Vue、Qwik 和其他非 React 框架中,使用
stylex.attrs(...)。 它返回一个普通的class字符串和一个字符串style值,用于绑定到 元素上。按照与stylex.props相同的顺序传递样式。
然后,无论你使用哪种应用器:
- 将结果直接应用到小写的宿主元素上,例如
div、span和button。 替换旧的className或class。 - 组件不会自动接受
stylex.props或stylex.attrs的输出。 通过styleprop 传递样式令牌,让组件将其应用到它 自己的宿主元素上。不要只是把应用器的输出丢到组件上,然后假设它能工作。 - 保留条件组合。应用器从左到右合并,最后一个
值胜出,与
cn(...)匹配。cn('base', isActive && 'active')变为stylex.props(styles.base, isActive && styles.active),或者使用stylex.attrs的相同调用。 传入的覆盖值保持最后,这样调用方仍然可以胜出。
步骤 5:在有意义时提取重复的设计令牌
如果品牌颜色、间距步长或其他主题值在文件中反复出现,或者
项目使用语义令牌,请考虑在 .stylex.ts 文件中使用 stylex.defineVars,而不是
硬编码它。使用 createTheme 的变量也可以替代基于类的 dark: 主题化。
请参阅 references/stylex-rules.md 中的主题化部分。不要对一次性
值强行这样做。
步骤 6:报告未转换的内容
在总结中,列出基于选择器的工具类、未解析的类,以及未直接 映射的标记更改。在没有警告的情况下丢失 hover、深色模式或响应式样式 是这里最重要的失败。
步骤 7:验证
对更改过的文件进行类型检查并运行 linter。如果项目有 StyleX ESLint
插件,它会捕获无效的简写、缺失的 default 和未知属性。修复
它报告的问题。确认迁移过的元素上没有残留的 className / Tailwind 导入。
只有在整个代码库迁移完成后,才移除 Tailwind 指令和配置,
而不是在一个文件之后。如果依赖项未安装,请说明,并将检查标记为
结构性检查,而不是声称它运行了。
完整示例
// Before
import { cn } from '@/lib/utils'
export function Badge({ active, className }: Props) {
return (
<span
className={cn(
'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold',
active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-600',
className,
)}
>
Status
</span>
)
}
// After
import * as stylex from '@stylexjs/stylex'
const styles = stylex.create({
base: {
display: 'inline-flex',
alignItems: 'center',
borderRadius: '9999px',
paddingInline: '0.625rem',
paddingBlock: '0.125rem',
fontSize: '0.75rem',
lineHeight: '1rem',
fontWeight: 600,
},
active: { backgroundColor: '#dcfce7', color: '#166534' },
inactive: { backgroundColor: '#f3f4f6', color: '#4b5563' },
})
export function Badge({ active, style }: Props) {
return (
<span
{...stylex.props(styles.base, active ? styles.active : styles.inactive, style)}
>
Status
</span>
)
}
text-xs 会同时生成 font-size 和 line-height,因此转换会保留这两者。
调用方的覆盖从 className 移到了 style,因为 StyleX 传递的是样式,
而不是类名字符串。cn 三元表达式变成了 stylex.props 三元表达式,保持相同的后者优先
顺序。此示例使用 React。在 Solid、Svelte、Vue 和 Qwik 中,styles 对象逐字节
完全相同。只有 stylex.props 调用变成了
stylex.attrs,然后你按照该框架绑定属性的方式绑定返回的 class/style。
参考文件
references/mapping.md包含 Tailwind 修饰符和变体表,以及处理space-x、divide、group、peer、深色模式、任意值、渐变、 伪元素、动画和sr-only的方法。当遇到 超出普通单值工具类的情况时,请阅读该文件。references/stylex-rules.md涵盖 StyleX 编写规则、常见错误、项目设置、 框架对stylex.props和stylex.attrs的支持,以及使用defineVars和createTheme进行主题化。在设置 StyleX、迁移非 React 框架时,或者当 ESLint 插件标记出问题时,请阅读该文件。