.cursor/docs/createStaticStyles_migration_guide.md
createStaticStyles 是 antd-style 提供的静态样式创建函数,相比 createStyles(hook 方案)具有零运行时开销的优势。样式在模块加载时计算一次,而不是每次组件渲染时计算。
cssVar.json 中有对应项readableColor(), chroma(), mix(), calc() 中使用 token 数值'ant')之前(createStyles):
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css, token }) => {
return {
root: css`
color: ${token.colorText};
font-size: ${token.fontSize}px;
`,
};
});
之后(createStaticStyles):
import { createStaticStyles } from 'antd-style';
export const styles = createStaticStyles(({ css, cssVar }) => {
return {
root: css`
color: ${cssVar.colorText};
font-size: ${cssVar.fontSize};
`,
};
});
之前:
import { useStyles } from './style';
const Component = () => {
const { styles, cx } = useStyles();
return <div className={cx(styles.root, className)} />;
};
之后:
import { cx } from 'antd-style';
import { styles } from './style';
const Component = () => {
return <div className={cx(styles.root, className)} />;
};
规则:
token.xxx → cssVar.xxxcssVar.fontSize 已经包含 px 单位,不需要再加 px示例:
// ❌ 错误
font-size: ${cssVar.fontSize}px; // cssVar.fontSize 已经是 "14px"
// ✅ 正确
font-size: ${cssVar.fontSize}; // 直接使用
特殊情况 - calc ():
// ❌ 错误
calc(${token.fontSize}px * 2.5)
// ✅ 正确
calc(${cssVar.fontSize} * 2.5) // cssVar.fontSize 已经包含单位
适用: 数值、字符串类型的 props
步骤:
style prop 设置 CSS 变量示例:
样式文件:
export const styles = createStaticStyles(({ css }) => {
return {
root: css`
width: var(--component-size, 24px);
height: var(--component-size, 24px);
`,
};
});
组件文件:
import { useMemo } from 'react';
const Component = ({ size = 24, style, ...rest }) => {
const cssVariables = useMemo<Record<string, string>>(
() => ({
'--component-size': `${size}px`,
}),
[size],
);
return (
<div
className={styles.root}
style={{
...cssVariables,
...style,
}}
{...rest}
/>
);
};
已优化示例:
Video: maxHeight, maxWidth, minHeight, minWidthScrollShadow: sizeMaskShadow: sizeColorSwatches: sizeGrid: rows, maxItemWidth, gapLayout: headerHeightFooter: contentMaxWidth适用: 简单的布尔值 props(2-3 个)
步骤:
cx 组合示例:
样式文件:
export const styles = createStaticStyles(({ css }) => {
return {
root: css`
/* base styles */
`,
root_closable_true: css`
/* closable styles */
`,
root_closable_false: css`
/* no closable styles */
`,
root_hasTitle_true: css`
/* has title styles */
`,
root_hasTitle_false: css`
/* no title styles */
`,
};
});
组件文件:
const Component = ({ closable, hasTitle }) => {
const className = cx(
styles.root,
styles[`root_closable_${!!closable}`],
styles[`root_hasTitle_${!!hasTitle}`],
);
return <div className={className} />;
};
已优化示例:
Alert: closable, hasTitle, showIcon → 8 个组合(2×2×2)Image: alwaysShowActions → 2 个样式StoryBook: noPadding → 2 个样式适用: 依赖 isDarkMode 的条件样式
有两种处理方式:
步骤:
Dark 和 Light 两个静态样式theme.isDarkMode 选择示例:
样式文件:
export const styles = createStaticStyles(({ css, cssVar }) => {
return {
rootDark: css`
background: ${cssVar.colorFillTertiary};
color: ${cssVar.colorTextLightSolid};
`,
rootLight: css`
background: ${cssVar.colorFillQuaternary};
color: ${cssVar.colorText};
`,
};
});
组件文件:
import { useThemeMode } from 'antd-style';
const Component = () => {
const { isDarkMode } = useThemeMode();
return (
<div
className={cx(
isDarkMode ? styles.rootDark : styles.rootLight
)}
/>
);
};
步骤:
Dark 和 Light 两个静态样式cva 中将 isDarkMode 作为 variant propisDarkMode 值示例:
样式文件:
import { createStaticStyles } from 'antd-style';
import { cva } from 'class-variance-authority';
export const styles = createStaticStyles(({ css, cssVar }) => {
return {
filledDark: css`
background: ${cssVar.colorFillTertiary};
color: ${cssVar.colorTextLightSolid};
`,
filledLight: css`
background: ${cssVar.colorFillQuaternary};
color: ${cssVar.colorText};
`,
outlined: css`
border: 1px solid ${cssVar.colorBorder};
`,
root: css`
/* base styles */
`,
};
});
export const variants = cva(styles.root, {
defaultVariants: {
isDarkMode: false,
variant: 'filled',
},
variants: {
isDarkMode: {
false: null,
true: null, // isDarkMode 本身不添加样式,通过 compoundVariants 组合
},
variant: {
filled: null, // variant 本身不添加样式,通过 compoundVariants 组合
outlined: styles.outlined,
},
},
compoundVariants: [
{
class: styles.filledDark,
isDarkMode: true,
variant: 'filled',
},
{
class: styles.filledLight,
isDarkMode: false,
variant: 'filled',
},
],
});
组件文件:
import { useThemeMode } from 'antd-style';
import { variants } from './style';
const Component = ({ variant = 'filled' }) => {
const { isDarkMode } = useThemeMode();
return (
<div
className={variants({ isDarkMode, variant })}
/>
);
};
优势:
useMemo 动态创建 variantscva 的设计理念已优化示例:
TypewriterEffect: textDark / textLight(方式 A)Collapse: filledDark / filledLight(可优化为方式 B)Hotkey: inverseThemeDark / inverseThemeLight(可优化为方式 B)GuideCard: filledDark / filledLight(可优化为方式 B)GradientButton: buttonDark / buttonLight(方式 A)适用: 使用响应式断点
步骤:
responsive from antd-styleresponsive.sm 替代 responsive.mobilecreateStyles 参数中移除 responsive示例:
之前:
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css, responsive }) => ({
root: css`
${responsive.mobile} {
padding: 12px;
}
`,
}));
之后:
import { createStaticStyles } from 'antd-style';
import { responsive } from 'antd-style';
export const styles = createStaticStyles(({ css }) => ({
root: css`
${responsive.sm} {
padding: 12px;
}
`,
}));
注意:
responsive.mobile → responsive.smresponsive 提供:xs, sm, md, lg, xl, xxl已优化示例:
Header: responsive.mobile → responsive.smFormModal: responsive.mobile → responsive.smHero: responsive.mobile → responsive.sm适用: 使用自定义 stylish 工具
步骤:
lobeStaticStylish from @/stylesstylish.xxx → lobeStaticStylish.xxx示例:
之前:
import { createStyles } from 'antd-style';
export const useStyles = createStyles(({ css, stylish }) => ({
root: css`
${stylish.blur};
${stylish.variantFilled};
`,
}));
之后:
import { createStaticStyles } from 'antd-style';
import { lobeStaticStylish } from '@/styles';
export const styles = createStaticStyles(({ css }) => ({
root: css`
${lobeStaticStylish.blur};
${lobeStaticStylish.variantFilled};
`,
}));
已优化示例:
Button: stylish.blur → lobeStaticStylish.blurHero: stylish.gradientAnimation → lobeStaticStylish.gradientAnimation适用: 使用动态 prefixCls 参数
步骤:
const prefixCls = 'ant'createStyles 参数中移除 prefixCls示例:
之前:
export const useStyles = createStyles(({ css }, prefixCls: string) => ({
root: css`
.${prefixCls}-button {
/* styles */
}
`,
}));
之后:
const prefixCls = 'ant';
export const styles = createStaticStyles(({ css }) => ({
root: css`
.${prefixCls}-button {
/* styles */
}
`,
}));
已优化示例:
Alert, Collapse, FormModal, Image, Burger, DraggablePanel, DraggableSideNav, Toc, ColorSwatches, EmojiPicker, Form, awesome/Features适用: 使用 readableColor() 计算对比色
规则:
readableColor(token.colorPrimary) → cssVar.colorTextLightSolid(主色背景用白色文字)readableColor(token.colorTextQuaternary) → cssVar.colorText(浅色背景用深色文字)示例:
之前:
import { readableColor } from 'polished';
export const useStyles = createStyles(({ css, token }) => ({
checked: css`
background-color: ${token.colorPrimary};
color: ${readableColor(token.colorPrimary)};
`,
}));
之后:
export const styles = createStaticStyles(({ css, cssVar }) => ({
checked: css`
background-color: ${cssVar.colorPrimary};
color: ${cssVar.colorTextLightSolid};
`,
}));
已优化示例:
Checkbox: readableColor(token.colorPrimary) → cssVar.colorTextLightSolid适用: 使用 rgba() 设置透明度
步骤:
color-mix() 函数color-mix(in srgb, ${cssVar.xxx} alpha%, transparent)示例:
之前:
import { rgba } from 'polished';
export const useStyles = createStyles(({ css, token }) => ({
root: css`
background-color: ${rgba(token.colorBgLayout, 0.4)};
`,
}));
之后:
export const styles = createStaticStyles(({ css, cssVar }) => ({
root: css`
background-color: color-mix(in srgb, ${cssVar.colorBgLayout} 40%, transparent);
`,
}));
已优化示例:
Header: rgba(cssVar.colorBgLayout, 0.4) → color-mix(...)FormModal: rgba(cssVar.colorBgContainer, 0) → color-mix(...)适用: 使用 keyframes 创建动画
步骤:
createStaticStyles 外部定义 keyframes示例:
之前:
export const useStyles = createStyles(({ css, keyframes }) => {
const spin = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
return {
icon: css`
animation: ${spin} 1s linear infinite;
`,
};
});
之后:
import { keyframes } from 'antd-style';
const spin = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
export const styles = createStaticStyles(({ css }) => ({
icon: css`
animation: ${spin} 1s linear infinite;
`,
}));
已优化示例:
Icon: keyframes 动画Skeleton: keyframes shimmer 动画不推荐的做法:
// ❌ 不推荐:在组件中动态创建 variants
export const createVariants = (isDarkMode: boolean) =>
cva(styles.root, {
variants: {
variant: {
filled: isDarkMode ? styles.filledDark : styles.filledLight,
},
},
});
// 组件中
const variants = useMemo(() => createVariants(isDarkMode), [isDarkMode]);
推荐的做法:
将 isDarkMode 作为 cva 的 variant prop(见场景 4 方式 B),这样:
useMemo 动态创建cva 的设计理念// ✅ 推荐:将 isDarkMode 作为 variant prop
export const variants = cva(styles.root, {
variants: {
isDarkMode: {
false: null,
true: null,
},
variant: {
filled: null,
},
},
compoundVariants: [
{
class: styles.filledDark,
isDarkMode: true,
variant: 'filled',
},
{
class: styles.filledLight,
isDarkMode: false,
variant: 'filled',
},
],
});
// 组件中
const { isDarkMode } = useThemeMode();
const className = variants({ isDarkMode, variant: 'filled' });
无法优化:
chroma() - 颜色计算库readableColor() - 需要运行时计算(但可以用 token 替代)mix() - 颜色混合计算calc() 中使用 token 数值进行复杂计算示例:
// ❌ 无法优化
const scale = chroma.bezier([token.colorText, backgroundColor]).scale().colors(6);
无法优化:
无法优化:
useTheme() hook 获取运行时值awesome/Giscus/style.ts 使用 useTheme() 获取主题值createStyles → createStaticStylestoken.xxx → cssVar.xxxpx 后缀(cssVar 已包含单位)responsive.mobile → responsive.sm(如果使用)stylish.xxx → lobeStaticStylish.xxx(如果使用)rgba() → color-mix()(如果使用)readableColor() → token 替换(如果使用)prefixCls 参数 → 硬编码 const prefixCls = 'ant'(如果使用)isDarkMode → 静态样式拆分(如果使用)useStyles() → import { styles } from './style'import { cx } from 'antd-style'(如果需要)import { useTheme } from 'antd-style'(如果需要 theme.isDarkMode)isDarkMode 条件 → theme.isDarkMode 判断(如果使用)isDarkMode 拆分responsive.mobile → responsive.smstylish → lobeStaticStylishreadableColor() → token 替换样式文件:
import { createStaticStyles } from 'antd-style';
export const styles = createStaticStyles(({ css, cssVar }) => ({
root: css`
padding: ${cssVar.padding};
color: ${cssVar.colorText};
border-radius: ${cssVar.borderRadius};
`,
}));
组件文件:
import { cx } from 'antd-style';
import { styles } from './style';
const Component = ({ className }) => {
return <div className={cx(styles.root, className)} />;
};
样式文件:
import { createStaticStyles } from 'antd-style';
export const styles = createStaticStyles(({ css, cssVar }) => ({
root: css`
width: var(--component-size, 24px);
height: var(--component-size, 24px);
background: ${cssVar.colorBgContainer};
`,
}));
组件文件:
import { cx } from 'antd-style';
import { useMemo } from 'react';
import { styles } from './style';
const Component = ({ size = 24, className, style, ...rest }) => {
const cssVariables = useMemo<Record<string, string>>(
() => ({
'--component-size': `${size}px`,
}),
[size],
);
return (
<div
className={cx(styles.root, className)}
style={{
...cssVariables,
...style,
}}
{...rest}
/>
);
};
样式文件:
import { createStaticStyles } from 'antd-style';
export const styles = createStaticStyles(({ css, cssVar }) => ({
rootDark: css`
background: ${cssVar.colorFillTertiary};
color: ${cssVar.colorTextLightSolid};
`,
rootLight: css`
background: ${cssVar.colorFillQuaternary};
color: ${cssVar.colorText};
`,
}));
组件文件:
import { cx, useTheme } from 'antd-style';
import { styles } from './style';
const Component = ({ className }) => {
const { theme } = useTheme();
return (
<div
className={cx(
theme.isDarkMode ? styles.rootDark : styles.rootLight,
className
)}
/>
);
};
pnpm run type-check适用: 组件中只使用 theme.isDarkMode 或其他 token 值
规则:
theme.isDarkMode,使用 const { isDarkMode } = useThemeMode() 替代theme.colorText, theme.borderRadius 等),使用 cssVar 替代useThemeMode() 比 useTheme() 更轻量,只返回 isDarkMode 值示例:
之前:
import { useTheme } from 'antd-style';
const Component = () => {
const theme = useTheme();
return (
<div className={theme.isDarkMode ? styles.dark : styles.light}>
{theme.colorText}
</div>
);
};
之后:
import { cssVar, useThemeMode } from 'antd-style';
const Component = () => {
const { isDarkMode } = useThemeMode();
return (
<div className={isDarkMode ? styles.dark : styles.light}>
{cssVar.colorText}
</div>
);
};
已优化示例:
AuroraBackground, Select, Input, Button, DatePicker, AutoComplete, InputNumber, InputPassword, InputOPT, TextArea, SpotlightCardItem, Spotlight, HotkeyInput - 只使用 isDarkMode → useThemeMode()Image, GradientButton, Empty, FileTypeIcon, FormSubmitFooter, CodeEditor, LobeChat, Drawer, Modal, Avatar, AvatarGroup, SkeletonAvatar, SkeletonButton, SkeletonTags, Callout, LobeHub, GridBackground, FolderIcon, FileIcon, TokenTag, ChatSendButton, AvatarUploader - 使用 token → cssVar无法优化的文件(需要保留 useTheme()):
useMermaid, useStreamMermaid, useHighlight, useStreamHighlight - 需要完整的 theme 对象传给第三方库Alert, Tag, Menu, EmojiPicker - 需要实际颜色值传给颜色计算函数SkeletonTitle, SkeletonTags - 需要数值进行数学运算GridShowcase, GridBackground/demos - 需要实际颜色值传给 rgba() 函数CustomFonts - 需要实际字符串值进行字符串拼接Giscus/style.ts - 需要实际颜色值传给 readableColor() 和 rgba() 函数(其他 token 已优化为 cssVar)注意事项:
useThemeMode() 只返回 { isDarkMode },不返回完整的 theme 对象cssVar 的值是字符串(如 "14px", "#ffffff"),可以直接在 JSX 中使用Math.round(theme.fontSize * 1.5)),需要保留 useTheme()createStaticStyles 迁移是一个渐进式的优化过程。对于简单的静态样式,可以直接转换;对于复杂的动态场景,需要根据具体情况选择合适的优化策略。关键是要理解每种场景的处理方式,并灵活运用 CSS 变量、静态样式拆分等技术。
useThemeMode():当组件只使用 theme.isDarkMode 时cssVar:当组件使用其他 token 值(颜色、尺寸等)时useTheme():当 token 需要用于数值计算或传给第三方库时