22 KiB
order | title |
---|---|
7 | 定制主题 |
Ant Design 设计规范和技术上支持灵活的样式定制,以满足业务和品牌上多样化的视觉需求,包括但不限于全局样式(主色、圆角、边框)和指定组件的视觉定制。
在 5.0 版本的 Ant Design 中,我们提供了一套全新的定制主题方案。不同于 4.x 版本的 less 和 CSS 变量,有了 CSS-in-JS 的加持后,动态主题的能力也得到了加强,包括但不限于:
- 支持动态切换主题;
- 支持同时存在多个主题;
- 支持针对某个/某些组件修改主题变量;
- ...
在 ConfigProvider 中配置主题
在 5.0 版本中我们把影响主题的最小元素称为 Design Token。通过修改 Design Token,我们可以呈现出各种各样的主题或者组件。
修改主题变量
通过在 ConfigProvider 中传入 theme
,可以配置主题。在升级 v5 后,将默认使用 v5 的主题,以下是将配置主题示例:
import React from 'react';
import { ConfigProvider, Button } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
token: {
colorPrimary: '#00b96b',
},
}}
>
<Button />
</ConfigProvider>
);
export default App;
这将会得到一个以
#00b96b
为主色的主题,以 Button 组件为例可以看到相应的变化:
修改组件变量 (Component Token)
除了整体的 Design Token,各个组件也会开放自己的 Component Token 来实现针对组件的样式定制能力,不同的组件之间不会相互影响。同样地,也可以通过这种方式来覆盖组件的其他 Design Token。
import React from 'react';
import { ConfigProvider, Radio, Checkbox } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
components: {
Radio: {
colorPrimary: '#00b96b',
},
},
}}
>
<Radio>Radio</Radio>
<Checkbox>Checkbox</Checkbox>
</ConfigProvider>
);
export default App;
通过这种方式,我们可以仅将 Radio 组件的主色改为
#00b96b
,而不会影响其他组件。
动态主题的其他使用方式
动态切换
在 v5 中,动态切换主题对用户来说是非常简单的,你可以在任何时候通过 ConfigProvider
的 theme
属性来动态切换主题,而不需要任何额外配置。
局部主题
可以嵌套使用 ConfigProvider
来实现局部主题的更换。在子主题中未被改变的 Design Token 将会继承父主题。
import React from 'react';
import { ConfigProvider, Button } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
token: {
colorPrimary: '#1677ff',
},
}}
>
<Button />
<ConfigProvider
theme={{
token: {
colorPrimary: '#1890ff',
},
}}
>
<Button />
</ConfigProvider>
</ConfigProvider>
);
export default App;
使用 Design Token
如果你希望使用当前主题下的 Design Token,我们提供了 useToken
这个 hook 来获取 Design Token。
import React from 'react';
import { Button, theme } from 'antd';
const { useToken } = theme;
const App: React.FC = () => {
const { token } = useToken();
return <Button style={{ backgroundColor: token.colorPrimary }}>Button</Button>;
};
export default App;
静态消费(如 less)
当你需要非 React 生命周期消费 Token 变量时,可以通过静态方法将其导出:
import { theme } from 'antd';
const { defaultAlgorithm, defaultSeed } = theme;
const mapToken = defaultAlgorithm(defaultSeed);
如果需要将其应用到静态样式编译框架,如 less 可以通过 less-loader 注入:
{
loader: "less-loader",
options: {
lessOptions: {
modifyVars: mapToken,
},
},
}
兼容包提供了变量转换方法用于转成 v4 的 less 变量,如需使用点击此处查看详情。
进阶使用
在 Design Token 中我们提供了一套更加贴合设计的三层结构,将 Design Token 拆解为 Seed Token、Map Token 和 Alias Token 三部分。这三组 Token 并不是简单的分组,而是一个三层的派生关系,由 Seed Token 派生 Map Token,再由 Map Token 派生 Alias Token。在大部分情况下,使用 Seed Token 就可以满足定制主题的需要。但如果您需要更高程度的主题定制,您需要了解 antd 中 Design Token 的生命周期。
演变过程
基础变量(Seed Token)
Seed Token 意味着所有设计意图的起源。比如我们可以通过改变 colorPrimary
来改变主题色,antd 内部的算法会自动的根据 Seed Token 计算出对应的一系列颜色并应用:
const theme = {
token: {
colorPrimary: '#1890ff',
},
};
梯度变量(Map Token)
Map Token 是基于 Seed 派生的梯度变量。定制 Map Token 推荐通过 theme.algorithm
来实现,这样可以保证 Map Token 之间的梯度关系。也可以通过 theme.token
覆盖,用于单独修改一些 map token 的值。
const theme = {
token: {
colorPrimaryBg: '#e6f7ff',
},
};
别名变量(Alias Token)
Alias Token 用于批量控制某些共性组件的样式,基本上是 Map Token 别名,或者特殊处理过的 Map Token。
const theme = {
token: {
colorLink: '#1890ff',
},
};
基本算法(algorithm)
基本算法用于将 Seed Token 展开为 Map Token,比如由一个基本色算出一个梯度色板,或者由一个基本的圆角算出各种大小的圆角。在 v5 中,我们默认提供了三种算法,分别是默认算法(defaultAlgorithm)、暗色算法(darkAlgorithm)和紧凑算法(compactAlgorithm)。算法可以任意地组合使用,比如可以将暗色算法和紧凑算法组合使用,得到一个暗色和紧凑相结合的主题。
import { theme } from 'antd';
const { darkAlgorithm, compactAlgorithm } = theme;
const theme = {
algorithm: [darkAlgorithm, compactAlgorithm],
};
兼容性调整
Ant Design 的 CSS-in-JS 默认通过 :where
选择器降低 CSS Selector 优先级。在某些场景下你如果需要支持的旧版游览器,你可以使用 @ant-design/cssinjs
取消默认的降权操作(请注意版本保持与 antd 一致):
import React from 'react';
import { StyleProvider } from '@ant-design/cssinjs';
export default () => (
<StyleProvider hashPriority="high">
<MyApp />
</StyleProvider>
);
切换后,样式将从 :where
切换为类选择器:
-- :where(.css-bAMboO).ant-btn {
++ .css-bAMboO.ant-btn {
color: #fff;
}
服务端渲染
使用 @ant-design/cssinjs
将所需样式抽离:
import React from 'react';
import { renderToString } from 'react-dom/server';
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
export default () => {
// SSR Render
const cache = createCache();
const html = renderToString(
<StyleProvider cache={cache}>
<MyApp />
</StyleProvider>,
);
// Grab style from cache
const styleText = extractStyle(cache);
// Mix with style
return `
<!DOCTYPE html>
<html>
<head>
${styleText}
</head>
<body>
<div id="root">${html}</div>
</body>
</html>
`;
};
API
Theme
属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
token | 用于修改 Design Token | AliasToken |
- |
inherit | 继承上层 ConfigProvider 中配置的主题。 | boolean | true |
algorithm | 用于修改 Seed Token 到 Map Token 的算法 | (token: SeedToken) => MapToken | ((token: SeedToken) => MapToken)[] |
defaultAlgorithm |
components | 用于修改各个组件的 Component Token 以及覆盖该组件消费的 Alias Token | OverrideToken | - |
OverrideToken
属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
Component (可以是任意 antd 组件名,如 Button ) |
用于修改 Component Token 以及覆盖该组件消费的 Alias Token | ComponentToken & AliasToken |
- |
SeedToken
属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
colorPrimary | 品牌主色 | string |
#1677ff |
colorSuccess | 成功色 | string |
#52c41a |
colorWarning | 警戒色 | string |
#faad14 |
colorError | 错误色 | string |
#f5222d |
colorInfo | 信息色 | string |
#1677ff |
colorTextBase | 基础文本色 | string |
#000 |
colorTextLightSolid | 亮色文本色 | string |
#fff |
colorBgBase | 基础背景色 | string |
#fff |
fontFamily | 字体 | string |
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji' |
fontSizeBase | 基础字号 | number |
14 |
gridUnit | - | number |
4 |
gridBaseStep | - | number |
2 |
lineWidth | 基础线宽 | number |
1 |
lineType | 线条样式 | string |
solid |
motionUnit | 动画时长变化单位 | number |
0.1 |
motionBase | 动画基础时长 | number |
0 |
motionEaseOutCirc | - | string |
cubic-bezier(0.08, 0.82, 0.17, 1) |
motionEaseInOutCirc | - | string |
cubic-bezier(0.78, 0.14, 0.15, 0.86) |
motionEaseOut | - | string |
cubic-bezier(0.215, 0.61, 0.355, 1) |
motionEaseInOut | - | string |
cubic-bezier(0.645, 0.045, 0.355, 1) |
motionEaseOutBack | - | string |
cubic-bezier(0.12, 0.4, 0.29, 1.46) |
motionEaseInQuint | - | string |
cubic-bezier(0.645, 0.045, 0.355, 1) |
motionEaseOutQuint | - | string |
cubic-bezier(0.23, 1, 0.32, 1) |
radiusBase | 基础圆角 | number |
6 |
sizeUnit | 尺寸变化单位 | number |
4 |
sizeBaseStep | 尺寸基础大小 | number |
4 |
sizePopupArrow | 组件箭头尺寸 | number |
16 |
controlHeight | - | number |
32 |
zIndexBase | 基础 z-index |
number |
0 |
zIndexPopupBase | 浮层基础 z-index |
number |
1000 |
opacityImage | - | number |
1 |
wireframe | 线框化 | boolean |
false |
MapToken
继承所有 SeedToken 的属性
属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
colorText | 一级文本色 | string |
rgba(0, 0, 0, 0.88) |
colorTextSecondary | 二级文本色 | string |
rgba(0, 0, 0, 0.65) |
colorTextTertiary | 三级文本色 | string |
rgba(0, 0, 0, 0.45) |
colorTextQuaternary | 四级文本色 | string |
rgba(0, 0, 0, 0.25) |
colorFill | 一级填充色 | string |
rgba(0, 0, 0, 0.15) |
colorFillSecondary | 二级填充色 | string |
rgba(0, 0, 0, 0.06) |
colorFillTertiary | 三级填充色 | string |
rgba(0, 0, 0, 0.04) |
colorFillQuaternary | 四级填充色 | string |
rgba(0, 0, 0, 0.02) |
colorBgContainer | 组件容器背景色 | string |
#ffffff |
colorBgElevated | 浮层容器背景色 | string |
#ffffff |
colorBgLayout | 布局背景色 | string |
#f5f5f5 |
colorBgSpotlight | - | string |
rgba(0, 0, 0, 0.85) |
colorBorder | 一级边框色 | string |
#d9d9d9 |
colorBorderSecondary | 二级边框色 | string |
#f0f0f0 |
colorSplit | 分割线颜色 | string |
rgba(0, 0, 0, 0.06) |
colorPrimaryBg | 主色的浅色背景颜色 | string |
#e6f4ff |
colorPrimaryBgHover | 主色的浅色背景色悬浮态 | string |
#bae0ff |
colorPrimaryBorder | 主色的描边色 | string |
#91caff |
colorPrimaryBorderHover | 主色的描边色悬浮态 | string |
#69b1ff |
colorPrimaryHover | 主色的深色悬浮态 | string |
#4096ff |
colorPrimary | 品牌主色 | string |
#1677ff |
colorPrimaryActive | 主色的深色激活态 | string |
#0958d9 |
colorPrimaryTextHover | 主色的文本悬浮态 | string |
#4096ff |
colorPrimaryText | 主色的文本默认态 | string |
#1677ff |
colorPrimaryTextActive | 主色的文本激活态 | string |
#0958d9 |
colorSuccessBg | 成功色的浅色背景颜色 | string |
#f6ffed |
colorSuccessBgHover | 成功色的浅色背景色悬浮态 | string |
#d9f7be |
colorSuccessBorder | 成功色的描边色 | string |
#b7eb8f |
colorSuccessBorderHover | 成功色的描边色悬浮态 | string |
#95de64 |
colorSuccessHover | 成功色的深色悬浮态 | string |
#95de64 |
colorSuccess | 成功色 | string |
#52c41a |
colorSuccessActive | 成功色的深色激活态 | string |
#389e0d |
colorSuccessTextHover | 成功色的文本悬浮态 | string |
#73d13d |
colorSuccessText | 成功色的文本默认态 | string |
#52c41a |
colorSuccessTextActive | 成功色的文本激活态 | string |
#389e0d |
colorWarningBg | 警戒色的浅色背景颜色 | string |
#fffbe6 |
colorWarningBgHover | 警戒色的浅色背景色悬浮态 | string |
#fff1b8 |
colorWarningBorder | 警戒色的描边色 | string |
#ffe58f |
colorWarningBorderHover | 警戒色的描边色悬浮态 | string |
#ffd666 |
colorWarningHover | 警戒色的深色悬浮态 | string |
#ffd666 |
colorWarning | 警戒色 | string |
#faad14 |
colorWarningActive | 警戒色的深色激活态 | string |
#d48806 |
colorWarningTextHover | 警戒色的文本悬浮态 | string |
#ffc53d |
colorWarningText | 警戒色的文本默认态 | string |
#faad14 |
colorWarningTextActive | 警戒色的文本激活态 | string |
#d48806 |
colorErrorBg | 错误色的浅色背景颜色 | string |
#fff1f0 |
colorErrorBgHover | 错误色的浅色背景色悬浮态 | string |
#ffccc7 |
colorErrorBorder | 错误色的描边色 | string |
#ffa39e |
colorErrorBorderHover | 错误色的描边色悬浮态 | string |
#ff7875 |
colorErrorHover | 错误色的深色悬浮态 | string |
#ff7875 |
colorError | 错误色 | string |
#ff4d4f |
colorErrorActive | 错误色的深色激活态 | string |
#cf1322 |
colorErrorTextHover | 错误色的文本悬浮态 | string |
#ff4d4f |
colorErrorText | 错误色的文本默认态 | string |
#f5222d |
colorErrorTextActive | 错误色的文本激活态 | string |
#cf1322 |
colorInfoBg | 信息色的浅色背景颜色 | string |
#e6f4ff |
colorInfoBgHover | 信息色的浅色背景色悬浮态 | string |
#bae0ff |
colorInfoBorder | 信息色的描边色 | string |
#91caff |
colorInfoBorderHover | 信息色的描边色悬浮态 | string |
#69b1ff |
colorInfoHover | 信息色的深色悬浮态 | string |
#69b1ff |
colorInfo | 信息色 | string |
#e6f4ff |
colorInfoActive | 信息色的深色激活态 | string |
#0958d9 |
colorInfoTextHover | 信息色的文本悬浮态 | string |
#4096ff |
colorInfoText | 信息色的文本默认态 | string |
#1677ff |
colorInfoTextActive | 信息色的文本激活态 | string |
#0958d9 |
colorBgMask | 浮层的背景蒙层颜色 | string |
rgba(0, 0, 0, 0.45) |
sizeSpace | - | number |
12 |
sizeSpaceSM | - | number |
16 |
sizeSpaceXS | - | number |
8 |
sizeSpaceXXS | - | number |
4 |
gridSpaceSM | - | number |
4 |
gridSpaceBase | - | number |
8 |
gridSpaceLG | - | number |
12 |
gridSpaceXL | - | number |
16 |
gridSpaceXXL | - | number |
28 |
lineWidthBold | 较粗的线宽 | number |
2 |
motionDurationFast | 动画速度快 | string |
0.1s |
motionDurationMid | 动画速度中等 | string |
0.2s |
motionDurationSlow | 动画速度慢 | string |
0.3s |
radiusXS | 更小的圆角 | number |
2 |
radiusSM | 较小的圆角 | number |
4 |
radiusLG | 较大的圆角 | number |
8 |
radiusOuter | 向外的圆角(常用于箭头与其他元素相接处) | number |
4 |
controlHeightXS | - | number |
24 |
controlHeightSM | - | number |
16 |
controlHeightLG | - | number |
40 |
AliasToken
继承所有 SeedToken 和 MapToken 的属性
属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
colorFillContent | - | string |
rgba(0, 0, 0, 0.06) |
colorFillContentHover | - | string |
rgba(0, 0, 0, 0.12) |
colorFillAlter | - | string |
rgba(0, 0, 0, 0.02) |
colorBgContainerDisabled | - | string |
rgba(0, 0, 0, 0.04) |
colorBorderBg | - | string |
#ffffff |
colorSplit | - | string |
rgba(0, 0, 0, 0.06) |
colorTextPlaceholder | - | string |
rgba(0, 0, 0, 0.25) |
colorTextDisabled | - | string |
rgba(0, 0, 0, 0.25) |
colorTextHeading | - | string |
rgba(0, 0, 0, 0.85) |
colorTextLabel | - | string |
rgba(0, 0, 0, 0.65) |
colorTextDescription | - | string |
rgba(0, 0, 0, 0.45) |
colorBgTextHover | - | string |
rgba(0, 0, 0, 0.06) |
colorBgTextActive | - | string |
rgba(0, 0, 0, 0.15) |
colorIcon | - | string |
rgba(0, 0, 0, 0.45) |
colorIconHover | - | string |
rgba(0, 0, 0, 0.88) |
colorLink | - | string |
#1677ff |
colorLinkHover | - | string |
#69b1ff |
colorLinkActive | - | string |
#0958d9 |
colorHighlight | - | string |
#ff4d4f |
controlOutline | - | string |
rgba(5, 145, 255, 0.1) |
colorWarningOutline | - | string |
rgba(255, 215, 5, 0.1) |
colorErrorOutline | - | string |
rgba(255, 22, 5, 0.06) |
fontSizeSM | - | number |
12 |
fontSize | - | number |
14 |
fontSizeLG | - | number |
16 |
fontSizeXL | - | number |
20 |
fontSizeIcon | - | number |
12 |
fontSizeHeading1 | - | number |
38 |
fontSizeHeading2 | - | number |
30 |
fontSizeHeading3 | - | number |
24 |
fontSizeHeading4 | - | number |
20 |
fontSizeHeading5 | - | number |
16 |
fontWeightStrong | - | number |
600 |
lineHeight | - | number |
1.5714 |
lineHeightLG | - | number |
1.5 |
lineHeightSM | - | number |
1.6667 |
lineHeightHeading1 | - | number |
1.2105 |
lineHeightHeading2 | - | number |
1.2667 |
lineHeightHeading3 | - | number |
1.3333 |
lineHeightHeading4 | - | number |
1.4 |
lineHeightHeading5 | - | number |
1.5 |
controlLineWidth | - | number |
1 |
controlLineType | - | string |
solid |
controlRadius | - | number |
6 |
controlRadiusXS | - | number |
2 |
controlRadiusSM | - | number |
4 |
controlRadiusLG | - | number |
8 |
controlOutlineWidth | - | number |
8 |
controlItemBgHover | - | string |
rgba(0, 0, 0, 0.04) |
controlItemBgActive | - | string |
#e6f4ff |
controlItemBgActiveHover | - | string |
#bae0ff |
controlInteractiveSize | - | number |
16 |
controlItemBgActiveDisabled | - | string |
rgba(0, 0, 0, 0.15) |
controlTmpOutline | - | string |
rgba(0, 0, 0, 0.02) |
opacityLoading | - | number |
0.65 |
padding | - | number |
16 |
paddingSM | - | number |
12 |
paddingXS | - | number |
8 |
paddingXXS | - | number |
4 |
paddingLG | - | number |
24 |
paddingXL | - | number |
32 |
paddingTmp | - | number |
20 |
margin | - | number |
16 |
marginSM | - | number |
12 |
marginXS | - | number |
8 |
marginXXS | - | number |
4 |
marginLG | - | number |
24 |
marginXL | - | number |
32 |
marginXXL | - | number |
48 |
boxShadow | - | string |
0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02), 0 2px 4px 0 rgba(0, 0, 0, 0.02) |
boxShadowSecondary | - | string |
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05) |
linkDecoration | - | React.CSSProperties['textDecoration'] |
none |
linkHoverDecoration | - | React.CSSProperties['textDecoration'] |
none |
linkFocusDecoration | - | React.CSSProperties['textDecoration'] |
none |
controlPaddingHorizontal | - | number |
12 |
controlPaddingHorizontalSM | - | number |
8 |
screenXS | - | number |
480 |
screenXSMin | - | number |
480 |
screenXSMax | - | number |
479 |
screenSM | - | number |
576 |
screenSMMin | - | number |
576 |
screenSMMax | - | number |
575 |
screenMD | - | number |
768 |
screenMDMin | - | number |
768 |
screenMDMax | - | number |
767 |
screenLG | - | number |
992 |
screenLGMin | - | number |
992 |
screenLGMax | - | number |
991 |
screenXL | - | number |
1200 |
screenXLMin | - | number |
1200 |
screenXLMax | - | number |
1199 |
screenXXL | - | number |
1600 |
screenXXLMin | - | number |
1599 |
screenXXLMax | - | number |
1600 |
调试主题
我们提供了帮助用户调试主题的工具:主题编辑器
你可以使用此工具自由地修改 Design Token,以达到您对主题的期望。
主题展示
FAQ
为什么 theme
从 undefined
变为对象或者变为 undefined
时组件重新 mount 了?
在 ConfigProvider 中我们通过 DesignTokenContext
传递 context,theme
为 undefined
时不会套一层 Provider,所以从无到有或者从有到无时 React 的 VirtualDOM 结构变化,导致组件重新 mount。解决方法:将 undefined
替换为空对象 {}
即可。