本教程所使用 antd 版本为截止发稿前的最新版本 5.25.1;同时默认读者了解并使用 antd 做组件开发。
1. 前言
antd 是当前最火的前端页面开发组件库之一,我们结合 antd 的 Design Token 可以实现一套方便自由统一的主题切换系统,但同时页面总有一些样式或组件细节是 antd 不能全部覆盖的,那就需要我们使用 less/tailwindcss/sass/css-in-js 等方式或者纯 css 自己写样式,如果同时我们想使用/继承 antd 主题中定义的一些样式,如主题色、文字色、border radius尺寸等,要怎么做呢?本篇主要讲通过 antd 暴露的 css 变量来实现主题样式的全局使用方法。
编者注:antd 官方文档已经指明了如何实现主题配置和切换,不过是分散在几篇文档中,本篇主要做汇总并且加一些自我理解和实践。
2. 快速开始
官方文档指路:定制主题 - Ant Design
2.1. 我们可以建一个全局的 theme.ts 文件来定义一些我们的主题样式(编者目前是只要 light 模式和 dark 模式)
// src/theme.ts
export const lightColors = {
'primary': '#2979FF', // 主色 - 高亮科技蓝
'accent': '#5C9CFF', // 辅助色 - 稍浅蓝,用于 hover 等
'selectedBg': '#D4EBFF', // 选中背景色 - 浅蓝
'background': '#F9FAFB', // 背景色 - 浅灰白
...
};
export const darkColors = {
'primary': '#5C9CFF', // 主色(提亮一点)
'accent': '#8CBBFF', // 辅助色(更亮)
'selectedBg': '#1d3466', // 选中背景色 - 浅蓝
'background': '#0F172A', // 背景色 - 深蓝黑
...
}
// 风格(比如 box-shadow 等)
export const lightStyle = {
'primaryShadow': '0 2px 0 rgb(42 121 255 / 10%)',
...
}
export const darkStyle = {
'primaryShadow': '0 2px 0 rgb(92 156 255 / 10%)',
...
}
2.2. 创建一个 antd 的主题配置文件
// antdThemeConfig.ts
import { lightColors, darkColors, lightStyle, darkStyle } from '../theme';
import { theme, ThemeConfig } from 'antd';
// 根据 isDark 判断获取哪种主题样式
export const getThemeConfig = (isDark: boolean): ThemeConfig => {
const colors = isDark ? darkColors : lightColors;
const styles = isDark ? darkStyle : lightStyle;
return {
token: { // 全局 Design Token 配置
colorPrimary: colors['primary'],
colorBgBase: colors['background'],
colorTextBase: colors['text'],
colorInfo: colors['primary'],
colorSuccess: colors['success'],
colorWarning: colors['warning'],
colorError: colors['danger'],
colorBorder: colors['divider'],
},
cssVar: {
key: 'r-theme',
},
hashed: false, // 根据官方文档,如果应用中只存在一个版本的 antd,可以选择关闭 hash 来进一步减小样式体积
algorithm: theme.defaultAlgorithm,
components: { // 组件 token 配置
Button: {
colorPrimaryHover: colors['accent'],
primaryShadow: styles['primaryShadow'],
dangerShadow: styles['dangerShadow'],
},
Menu: {
itemActiveBg: styles['itemActiveBg'],
itemSelectedBg: colors['selectedBg'],
},
Layout: {
headerBg: colors['card'],
},
},
};
}
文档中的配置属性说明如下:
- token
按照官方文档,token 分为基础变量(Seed Token)、梯度变量(Map Token)、别名变量(Alias Token),简单来说的话,Seed Token 是后面衍生出梯度变量和别名变量的基础,因为 antd 内部有一套计算这些变量的算法(下面就有)。我的建议是我们在这个属性下面主要可以设定一些基础主题变量(Seed Token),比如 colorPrimary 等,Seed Token 的全量列表可以看这里 - ant.design/docs/react/…
- algorithm
默认是 defaultAlgorithm,还有 compactAlgorithm, darkAlgorithm,可以组合使用。从目前看下来,有一些梯度颜色生成的不是特别好,所以需要我们根据情况自己再去设定。
- cssVar
从名字就可以看出来,这个属性表示 antd 内部的 css 变量,设置这个可以将他的所有 css 变量暴露出来供我们自己设置样式时使用,我强烈推荐配置这个变量。 至于里面的 key 等配置大家可以看官方文档自行了解,暂不赘述
- hashed
官方文档建议如果项目只有一个 antd 版本,可以选择关闭 hashed,这样可以进一步减少体积。
- components
官方说明:“用于修改 Component Token 以及覆盖该组件消费的 Alias Token”。除了通用的 Design Token,antd 的每个组件有自己特定的一些 token,如果要对特定组件的样式做修改,可以放在 components 下面进行配置,这样可以统一所有该组件在项目中的样式,比如说希望全局的 modal 样式是统一的,那么我们可以直接在这里去配置样式,而不是不同的开发者按照设计稿又自己修改样式,导致代码冗余。
2.3. 主题切换
接下来我们来看一下如何使用这些配置
- 创建 ThemeContext
// context/ThemeContext.tsx
import React, { createContext, useContext, useState, useEffect } from "react";
export type ThemeContextType = {
isDark: boolean;
toggleTheme: () => void;
};
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
// 初始化时读取 localStorage 或系统偏好
const savedTheme = localStorage.getItem("theme") === "dark";
setIsDark(savedTheme);
}, []);
const toggleTheme = () => {
const newTheme = !isDark;
setIsDark(newTheme);
localStorage.setItem("theme", newTheme ? "dark" : "light");
document.documentElement.classList.toggle("dark", newTheme);
};
return (
<ThemeContext.Provider value={{ isDark, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) throw new Error("useTheme must be used within ThemeProvider");
return context;
};
- 入口文件添加以下代码
// 入口文件 App.tsx 或者根 index.tsx
import { RouterProvider } from "react-router";
import { Provider } from "react-redux";
import { ThemeProvider, useTheme } from "@/context/ThemeContext";
const AppContent: React.FC = () => {
const { isDark } = useTheme();
useEffect(() => {
if (isDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, [isDark]);
return (
<ThemeProvider>
<Provider store={store}>
<ConfigProvider theme={getThemeConfig(isDark)}>
<RouterProvider router={router} />
</ConfigProvider>
</Provider>
</ThemeProvider>
);
}
注:以上第 7-13 行至关重要,这块代码是实现 tailwindcss 可以切换暗黑模式的重点
- 添加切换主题按钮
// ../layout.tsx
import { useTheme } from "@/context/ThemeContext";
import { SunOutlined, MoonOutlined } from "@ant-design/icons";
const MainLayout = () => {
const { isDark, toggleTheme } = useTheme();
{...} // 省略
return (
<>
{...}
<Segmented
size="small"
shape="round"
options={[
{ value: 'light', icon: <SunOutlined /> },
{ value: 'dark', icon: <MoonOutlined /> },
]}
onChange={toggleTheme}
value={isDark ? 'dark' : 'light'}
/>
</>
)
示例
2.4. tailwind css (v4+)使用
- 首先需要配置 tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
darkMode: 'class',
plugins: [],
};
注意这儿的darkMode要设置为class.
- 使用代码
// tailwind css 使用,可以直接使用 antd 暴露出来的变量
<Button onClick={handleReset} className='bg-[var(--ant-color-error)]! dark:bg-[var(--ant-color-warning)]!'>重置</Button>
// dark 模式的样式配置
<Button onClick={handleReset} className='bg-[var(--ant-color-error)]! dark:bg-[var(--ant-color-warning)]!'>重置</Button>
2.5. less 文件使用
可直接使用 antd 的 css 变量
/* less 文件 */
.ant-table-thead > tr > th {
text-align: center !important;
font-weight: 500;
background-color: var(--ant-color-primary);
}
2.6. 我们如何知道要修改哪个 Design Token 值呢(重要❗️)
接下来就是一些细节问题了,我们目前配置了大方向的主题,包括主色,背景色,文字色,但是可能有一些组件属性,比如 box-shadow,他跟我们的主色并不相配,如下图所示,我们发现在暗黑模式下的按钮显示有问题
经过排查,发现是以下这个属性的原因,他所对应的变量是 --ant-button-primary-shadow
那么,这个 css 变量对应的 Design Token 是什么呢?当然一种办法是我们可以在 global.css 中修改
--ant-button-primary-shadow 变量的值来实现我们的目的,但是我们需要有两个值,他们分别是在 light 模式和 dark 模式下,那在 css 中怎么判断当前是出于 light 还是 dark 模式呢,这个我暂时还没有好的办法;所以还有一种办法就是找到他的 Design Token,我们直接在前文提到的 antdThemeConfig.ts 中修改,那么怎么寻找呢?
- 先看这个变量是哪个组件的
我们这个是 Button 组件
- 然后去对应组件的官方文档下去查找他对应的 Design Token
我们去查阅 ant.design/components/… 官方文档,果然找到了他的 Design Token
- 接下来我们可以修改我们的配置文件了
// theme.ts
export const lightStyle = {
'primaryShadow': '0 2px 0 rgb(42 121 255 / 10%)',
}
export const darkStyle = {
'primaryShadow': '0 2px 0 rgb(92 156 255 / 10%)',
}
// antdThemeConfig.ts
export const getThemeConfig = (isDark: boolean): ThemeConfig => {
...
return {
token: {
...
},
cssVar: {
...
},
hashed: false, // 根据官方文档,如果应用中只存在一个版本的 antd,你可以选择关闭 hash 来进一步减小样式体积
algorithm: theme.defaultAlgorithm,
components: {
Button: {
primaryShadow: styles['primaryShadow'],
},
...
},
};
}
大功告成!
3. Bonus时间: 插件
- cursor 可以安装 Tailwind CSS IntelliSense,写 tailwind 语法会有提示
- 可以安装 Color Highlight 插件,可以高亮颜色