如何基于Vanilla Extract CSS实现运行时动态主题更新
在Vanilla Extract中实现运行时主题更新的解决方案
问题核心
你当前直接在Vanilla Extract样式文件中导入theme变量的写法无效,因为Vanilla Extract是静态CSS生成工具,编译时就会把theme的初始值固化到CSS里,无法响应运行时的主题变化,所以生成的样式类没有正确赋值动态颜色。
解决方案:用CSS变量实现动态主题
Vanilla Extract官方推荐通过CSS变量实现运行时主题切换,核心思路是先定义主题变量映射,再在运行时动态修改变量值,样式文件直接引用这些变量。
1. 定义主题CSS变量
创建主题变量文件,用createTheme定义需要的变量映射:
// themes.css.ts import { createTheme } from '@vanilla-extract/css'; // 对应你主题中的颜色字段定义变量 export const [themeClass, vars] = createTheme({ colors: { ocean30: 'var(--amp-ocean-30)' } });
2. 修改ThemeProvider,动态注入CSS变量
调整NewThemeProvider,在运行时根据当前主题把颜色值注入到CSS变量中:
import { createContext, useEffect, useMemo } from 'react'; import { ThemeManager } from './ThemeManager'; import { ThemeContextProps, ThemeProviderProps } from './ThemeProvider.d'; import { merge } from 'lodash'; import { amplienceStyleTokens } from '../themes/styleTokens'; import { ThemeOverrideOther } from '../themes'; import { vars } from './themes.css'; export const ThemeContext = createContext<ThemeContextProps | null>(null); export const NewThemeProvider = ({ light, dark, theme: themeObj, colorScheme = 'light', ...props }: ThemeProviderProps) => { // 合并生成当前激活的主题 const activeTheme = useMemo(() => { const baseTheme = colorScheme === 'dark' ? dark : light || {}; return merge({}, baseTheme, themeObj || {}, amplienceStyleTokens); }, [colorScheme, light, dark, themeObj]); // 运行时将主题值注入到CSS变量 useEffect(() => { const root = document.documentElement; if (activeTheme.colors?.amp_ocean) { Object.entries(activeTheme.colors.amp_ocean).forEach(([key, value]) => { // 匹配vars中定义的变量名 const varKey = key.replace('amp_ocean_', '') as '30'; const varName = vars.colors[`ocean${varKey}` as keyof typeof vars.colors]; if (varName && value) { root.style.setProperty(varName, value); } }); } }, [activeTheme]); return ( <ThemeContext.Provider value={{ light, dark, colorScheme, theme: activeTheme, }} > <ThemeManager {...props} /> </ThemeContext.Provider> ); };
3. 在样式文件中引用主题变量
修改样式文件,直接使用vars中定义的变量,不再导入theme:
// test.styles.css.ts import { style } from '@vanilla-extract/css'; import { vars } from './themes.css'; export const root = style({ color: vars.colors.ocean30, });
4. 更新Storybook测试代码
确保测试组件应用themeClass,并正确包裹在NewThemeProvider中:
import { Meta, StoryFn } from '@storybook/react'; import { NewThemeProvider } from './NewThemeProvider'; import { useTheme } from './hooks/useThemePicker'; import * as classes from './test.styles.css'; import { themeClass } from './themes.css'; export default { title: 'core/atoms/A', component: NewThemeProvider, } as Meta; const TestComponent = () => { const hookTheme = useTheme(); console.log('Color from Hook'); console.log(hookTheme?.colors?.amp_ocean.amp_ocean_30); return <div className={`${themeClass} ${classes.root}`}>sdfcdsf</div>; }; const Template: StoryFn<any> = (args) => { return ( <NewThemeProvider> <TestComponent /> </NewThemeProvider> ); }; export const Caption = Template.bind({}); Caption.args = { text: 'Caption', };
关键说明
- Vanilla Extract是静态编译工具,不能直接引用运行时动态变化的JS变量,必须通过CSS变量作为中间层实现动态更新。
- 如果需要局部主题(而非全局),可以把CSS变量注入到
ThemeManager的根元素上,而非document.documentElement。 - 多主题切换时,可以为不同主题预设对应的CSS变量值,或在切换
colorScheme时重新注入变量。
内容的提问来源于stack exchange,提问作者Snivio
相关产品推荐
相关产品推荐

