# How Dify Chat Ensures Visual Consistency with Its Theme Package: CSS Variables and Design Tokens

> Discover how Dify Chat's theme package maintains visual consistency using CSS variables and design tokens, ensuring pixel-perfect design across light, dark, and system modes.

- Repository: [lexmin0412/dify-chat](https://github.com/lexmin0412/dify-chat)
- Tags: deep-dive
- Published: 2026-03-06

---

**Dify Chat centralizes all visual design tokens in the `@dify-chat/theme` package and distributes them through CSS custom properties, a React context provider, and a shared color configuration to guarantee pixel-perfect consistency across light, dark, and system modes.**

The `lexmin0412/dify-chat` repository solves the challenge of maintaining a unified visual language across multiple UI frameworks by implementing a centralized theming system. By storing every color value as a CSS variable and exposing them through a dedicated theme package, Dify Chat ensures visual consistency with its theme package regardless of whether users select light, dark, or system preferences.

## CSS Custom Properties as the Source of Truth

All visual colors are stored as CSS variables in [`packages/react-app/src/App.css`](https://github.com/lexmin0412/dify-chat/blob/main/packages/react-app/src/App.css). The `:root` selector defines light mode values, while the `.dark` class overrides them for dark mode:

```css
/* packages/react-app/src/App.css */
:root {
  --theme-text-color: #333;
  --theme-bg-color: #f2f4f7;
  /* …other vars… */
}

/* dark mode overrides */
.dark {
  --theme-text-color: #c9c9c9;
  --theme-bg-color: #000;
  /* …other vars… */
}

```

These variables are referenced using Tailwind’s arbitrary value syntax or custom class shortcuts. Because the variables live in one file, any color tweak instantly propagates to every UI element.

## Runtime Switching via ThemeContextProvider

The `ThemeContextProvider` in [`packages/theme/src/hooks/index.tsx`](https://github.com/lexmin0412/dify-chat/blob/main/packages/theme/src/hooks/index.tsx) manages the current theme state and mode selection. It listens to system preferences and updates the DOM accordingly:

```tsx
// packages/theme/src/hooks/index.tsx
useEffect(() => {
  const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
  if (themeMode === ThemeModeEnum.SYSTEM) {
    handleColorSchemeChange(mediaQuery)          // initialise
    mediaQuery.addEventListener('change', handleColorSchemeChange)
  } else {
    // manual mode – force class & CSS vars
    themeMode === ThemeModeEnum.DARK
      ? (document.body.classList.add(DARK_CLASS_NAME), setThemeState(ThemeEnum.DARK))
      : (document.body.classList.remove(DARK_CLASS_NAME), setThemeState(ThemeEnum.LIGHT))
  }
}, [themeMode])

```

When the mode changes, the provider toggles the `dark` class on `<body>` and updates CSS variables, causing all consuming components to re-render with the correct palette.

## Bridging Tokens to Tailwind and Ant Design

The `colors` object in [`packages/react-app/src/theme/config.ts`](https://github.com/lexmin0412/dify-chat/blob/main/packages/react-app/src/theme/config.ts) maps CSS variable names to Tailwind and Ant Design tokens:

```ts
// packages/react-app/src/theme/config.ts
export const colors = {
  'theme-text': 'var(--theme-text-color)',
  'theme-bg':   'var(--theme-bg-color)',
  // …other tokens…
}

```

**Tailwind integration**: The colors map is merged into [`tailwind.config.js`](https://github.com/lexmin0412/dify-chat/blob/main/tailwind.config.js), enabling utility classes like `bg-theme-bg` and `text-theme-text`.

**Ant Design integration**: The same map populates the theme token object in the application root:

```tsx
<XProvider theme={{ token: { colorPrimary: colors.primary, colorText: colors['theme-text'] } }} />

```

This dual integration ensures both styling systems read from the exact same source, preventing visual drift between framework components and custom elements.

## Implementing Consistent UI Components

All UI markup uses token-based class names that resolve to the current CSS variable value. For example, the authentication page uses:

```tsx
<div className="flex h-screen w-screen flex-col items-center justify-center bg-theme-bg">
  <div className="text-theme-text">授权登录中…</div>
</div>

```

Because these class names remain constant while the underlying CSS variables change, components maintain visual consistency across theme switches without conditional rendering logic.

## Consuming the Theme Package

To use the theme system, wrap your application with the provider in [`packages/react-app/src/index.tsx`](https://github.com/lexmin0412/dify-chat/blob/main/packages/react-app/src/index.tsx):

```tsx
import { ThemeContextProvider } from '@dify-chat/theme'

const Root = () => (
  <ThemeContextProvider>
    <App />
  </ThemeContextProvider>
)

```

Access the theme state in any component using the `useThemeContext` hook:

```tsx
import { useThemeContext } from '@dify-chat/theme'

export const SampleBox = () => {
  const { isDark, themeMode, setThemeMode } = useThemeContext()

  return (
    <div className="p-4 bg-theme-bg text-theme-text">
      当前模式：{themeMode} （{isDark ? '暗色' : '浅色'}）
      <button onClick={() => setThemeMode('light')}>浅色</button>
      <button onClick={() => setThemeMode('dark')}>暗色</button>
    </div>
  )
}

```

To embed a theme selector in your layout, use the `ThemeSelector` component from [`packages/react-app/src/components/layout/header.tsx`](https://github.com/lexmin0412/dify-chat/blob/main/packages/react-app/src/components/layout/header.tsx):

```tsx
import { ThemeSelector } from '@dify-chat/theme'

export const Header = () => (
  <header>
    {/* other header content */}
    <ThemeSelector>
      <button className="text-theme-text">切换主题</button>
    </ThemeSelector>
  </header>
)

```

## Summary

- **CSS Variables**: All color values are defined in [`packages/react-app/src/App.css`](https://github.com/lexmin0412/dify-chat/blob/main/packages/react-app/src/App.css) and scoped to `:root` and `.dark` selectors, creating a single source of truth for the entire application.
- **Context Provider**: The `ThemeContextProvider` handles runtime switching between light, dark, and system modes by manipulating the `dark` class on `<body>` and listening to `prefers-color-scheme` media queries.
- **Design Tokens**: The `colors` configuration in [`packages/react-app/src/theme/config.ts`](https://github.com/lexmin0412/dify-chat/blob/main/packages/react-app/src/theme/config.ts) bridges CSS variables to both Tailwind CSS and Ant Design, ensuring framework-agnostic visual consistency.
- **Component Integration**: UI elements use token-based class names (e.g., `bg-theme-bg`) that automatically resolve to the correct color values regardless of the active theme.

## Frequently Asked Questions

### How does Dify Chat handle system-level dark mode preferences?

The `ThemeContextProvider` initializes a `window.matchMedia('(prefers-color-scheme: dark)')` listener when the theme mode is set to `SYSTEM`. When the media query changes, the provider automatically toggles the `dark` class on the document body and updates the theme state, ensuring the UI reflects OS-level preferences without requiring a page refresh.

### Can I use Dify Chat theme tokens in custom components?

Yes. Any component within the `ThemeContextProvider` tree can import `useThemeContext` from `@dify-chat/theme` to access the current theme state. For styling, use the predefined token classes like `bg-theme-bg` or `text-theme-text`, which resolve to the active CSS variables defined in [`packages/react-app/src/App.css`](https://github.com/lexmin0412/dify-chat/blob/main/packages/react-app/src/App.css).

### Where are the theme color values actually defined?

The raw color values are defined as CSS custom properties in [`packages/react-app/src/App.css`](https://github.com/lexmin0412/dify-chat/blob/main/packages/react-app/src/App.css) within the `:root` and `.dark` selectors. These variables are then mapped to JavaScript-friendly tokens in [`packages/react-app/src/theme/config.ts`](https://github.com/lexmin0412/dify-chat/blob/main/packages/react-app/src/theme/config.ts) for consumption by Tailwind and Ant Design configurations.

### What happens when a user manually switches themes?

When `setThemeMode` is called with `'light'` or `'dark'`, the provider immediately updates the local storage persistence, toggles the `dark` class on `<body>`, and sets the internal theme state. This triggers a re-render of all components using `useThemeContext`, while CSS variables update automatically, ensuring instant visual feedback without flashing or layout shifts.