You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 18:17:03