테마 커스터마이징
테마 커스터마이징 (Customize Theme)
Ant Design은 디자인 토큰을 커스터마이징해서 비즈니스나 브랜드 요구에 맞는 다양한 UI를 만들 수 있게 해 줘요. primary color, border radius, border color 등을 자유롭게 바꿀 수 있어요.
출처: 문서
본문
Ant Design은 디자인 토큰을 커스터마이징해서 비즈니스나 브랜드 요구에서 나온 UI 다양성을 충족시킬 수 있게 해 줘요. 여기에는 primary color, border radius, border color 등이 포함돼요.
5.0부터 테마를 커스터마이징하는 새로운 방식을 제공해요. 4.x 버전의 less나 CSS 변수 방식과 달리, CSS-in-JS를 사용하면 테마링 능력도 한층 강화되는데, 그 예는 다음과 같아요 (이것만 있는 건 아니에요):
- 테마를 동적으로 전환하기;
- 여러 테마 지원;
- 일부 컴포넌트에 대한 테마 변수 커스터마이징;
- ...
기본 사용법
테마에 영향을 주는 가장 작은 요소를 Design Token이라고 불러요. Design Token을 수정하면 다양한 테마나 컴포넌트를 표현할 수 있어요. ConfigProvider에 theme을 넘겨 테마를 커스터마이징할 수 있습니다.
:::warning
ConfigProvider는 message.xxx, Modal.xxx, notification.xxx 같은 정적 메서드에는 적용되지 않아요. 이런 메서드에서는 antd가 ReactDOM.render로 React 엔티티를 동적으로 새로 만들기 때문이에요. 그 컨텍스트가 현재 코드의 컨텍스트와 다르기 때문에 컨텍스트 정보를 얻을 수 없어요.
컨텍스트 정보(예: ConfigProvider가 구성한 내용)가 필요하다면, Modal.useModal 메서드로 모달 엔티티와 contextHolder 노드를 반환받아 컨텍스트가 필요한 위치에 삽입하면 돼요. 또는 App 컴포넌트를 사용해서 useModal처럼 contextHolder를 직접 심어야 하는 문제를 간단히 해결할 수도 있어요.
:::
Design Token 커스터마이징
theme의 token 속성을 수정하면 Design Token을 전역으로 수정할 수 있어요. 일부 토큰은 다른 토큰에도 영향을 주는데, 이런 토큰을 Seed Token이라고 불러요.
Design Token 커스터마이징
theme의 token 속성을 통해 일부 테마 변수를 수정할 수 있어요. 어떤 테마 변수는 다른 테마 변수의 변화를 일으키기도 하는데, 이를 Seed Token이라고 해요.
import React from 'react';
import { Button, ConfigProvider, Space } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
token: {
// Seed Token, affects wide range
colorPrimary: '#00b96b',
borderRadius: 2,
// Derived token, affects narrow range
colorBgContainer: '#f6ffed',
},
}}
>
<Space>
<Button type="primary">Primary</Button>
<Button>Default</Button>
</Space>
</ConfigProvider>
);
export default App;
프리셋 알고리즘 사용하기
algorithm을 수정하면 스타일이 다른 테마를 빠르게 생성할 수 있어요. 기본적으로 세 가지 프리셋 알고리즘을 제공해요:
- default 알고리즘
theme.defaultAlgorithm - dark 알고리즘
theme.darkAlgorithm - compact 알고리즘
theme.compactAlgorithm
ConfigProvider의 theme에 있는 algorithm 속성을 수정해서 알고리즘을 전환할 수 있어요.
프리셋 알고리즘 사용하기
알고리즘을 수정하면 서로 다른 테마를 빠르게 생성할 수 있어요. 기본적으로 theme.defaultAlgorithm, theme.darkAlgorithm, theme.compactAlgorithm 세 가지 프리셋 알고리즘을 제공해요. theme의 algorithm 속성으로 알고리즘을 전환할 수 있고, 여러 알고리즘을 배열로 구성하면 순서대로 적용돼요.
import React from 'react';
import { Button, ConfigProvider, Input, Space, theme } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
// 1. Use dark algorithm alone
algorithm: theme.darkAlgorithm,
// 2. Combine dark algorithm and compact algorithm
// algorithm: [theme.darkAlgorithm, theme.compactAlgorithm],
}}
>
<Space>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
);
export default App;
Component Token 커스터마이징
Design Token 외에도 각 컴포넌트는 고유한 Component Token을 가져서 컴포넌트별 스타일 커스터마이징을 지원하고, 컴포넌트끼리 서로 영향을 주지 않아요. 마찬가지로 컴포넌트가 사용하는 다른 Design Token도 이 방식으로 덮어쓸 수 있어요.
:::info{title=Component Token의 Algorithm} 기본적으로 모든 컴포넌트 토큰은 전역 토큰만 덮어쓸 수 있고, Seed Token을 기반으로 파생되지는 않아요.
>= 5.8.0 버전부터 컴포넌트 토큰은 algorithm 속성을 지원해서, 알고리즘을 활성화하거나 다른 알고리즘을 전달할 수 있어요.
:::
Component Token 커스터마이징
전체 Design Token 외에도 각 컴포넌트는 고유한 Component Token을 노출해서 컴포넌트별 스타일 커스터마이징을 가능하게 하고, 서로 다른 컴포넌트끼리 영향을 주지 않아요. 마찬가지로 이 방법으로 컴포넌트가 소비하는 다른 Design Token도 덮어쓸 수 있어요. >= 5.8.0 버전부터 컴포넌트 토큰은 algorithm 속성을 전달해 파생 계산을 활성화하거나 다른 알고리즘을 전달할 수 있어요.
import React from 'react';
import { Button, ConfigProvider, Divider, Input, Space } from 'antd';
const App: React.FC = () => (
<>
<ConfigProvider
theme={{
components: {
Button: {
colorPrimary: '#00b96b',
algorithm: true, // Enable algorithm
},
Input: {
colorPrimary: '#eb2f96',
algorithm: true, // Enable algorithm
},
},
}}
>
<Space>
<div style={{ fontSize: 14 }}>Algorithm Enabled:</div>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
<Divider />
<ConfigProvider
theme={{
components: {
Button: {
colorPrimary: '#00b96b',
},
Input: {
colorPrimary: '#eb2f96',
},
},
}}
>
<Space>
<div style={{ fontSize: 14 }}>Algorithm Disabled:</div>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
</>
);
export default App;
애니메이션 끄기
antd에는 엔터프라이즈 페이지를 더 세밀하게 만들기 위한 상호작용 애니메이션이 내장돼 있어요. 극단적인 일부 시나리오에서는 페이지 상호작용 성능에 영향을 줄 수 있어요. 애니메이션을 꺼야 한다면 token의 motion을 false로 설정해 보세요:
애니메이션 끄기
Ant Design은 엔터프라이즈 페이지를 더 세밀하게 만들기 위한 내장 컴포넌트 상호작용 애니메이션을 포함해요. 극단적인 시나리오에서는 페이지 상호작용 성능에 영향을 줄 수 있어요. 애니메이션을 꺼야 한다면 token의 motion을 false로 설정할 수 있어요.
import React, { useEffect, useRef, useState } from 'react';
import { Checkbox, Col, ConfigProvider, Flex, Radio, Row, Switch } from 'antd';
const App: React.FC = () => {
const [checked, setChecked] = useState<boolean>(false);
const timerRef = useRef<ReturnType<typeof setInterval>>(null);
useEffect(() => {
timerRef.current = setInterval(() => {
setChecked((prev) => !prev);
}, 500);
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
const nodes = (
<Flex gap="small">
<Checkbox checked={checked}>Checkbox</Checkbox>
<Radio checked={checked}>Radio</Radio>
<Switch checked={checked} />
</Flex>
);
return (
<Row gutter={[24, 24]}>
<Col span={24}>{nodes}</Col>
<Col span={24}>
<ConfigProvider theme={{ token: { motion: false } }}>{nodes}</ConfigProvider>
</Col>
</Row>
);
};
export default App;
고급 (Advanced)
제로 런타임 {#zero-runtime}
6.0.0부터 애플리케이션 성능을 더 끌어올리기 위해 zeroRuntime 모드를 제공해요. 이를 활성화하면 Ant Design이 더 이상 런타임에서 컴포넌트 스타일을 생성하지 않으므로, 스타일 파일을 수동으로 임포트해야 해요.
import 'antd/dist/antd.css';
export default () => (
<ConfigProvider theme={{ zeroRuntime: true }}>
<App />
</ConfigProvider>
);
antd/dist/antd.css에는 모든 antd 컴포넌트 스타일이 담겨 있지만 hashed className은 포함되지 않아요. 더 적은 스타일을 임포트하고 싶거나, prefix 같은 설정 변경 때문에 기본 스타일을 사용할 수 없다면, @ant-design/static-style-extract로 정적 스타일을 생성하는 걸 권장해요.
import fs from 'fs';
import { extractStyle } from '@ant-design/static-style-extract';
const cssText = extractStyle({
includes: ['Button'], // Only include Button component styles
});
fs.writeFileSync('/path/to/somewhere', cssText);
테마 동적 전환
v5에서 테마를 동적으로 전환하는 건 사용자에게 아주 간단해요. 추가 설정 없이 ConfigProvider의 theme 속성만으로 언제든 테마를 동적으로 전환할 수 있어요.
테마 동적 전환
v5에서 동적 테마 전환은 사용자에게 아주 간단해요. 추가 설정 없이 ConfigProvider의 theme 속성을 통해 언제든 테마를 동적으로 전환할 수 있어요.
import React, { useState } from 'react';
import { Button, ColorPicker, ConfigProvider, Divider, Input, Space } from 'antd';
const App: React.FC = () => {
const [primary, setPrimary] = useState('#1677ff');
return (
<>
<ColorPicker showText value={primary} onChange={(color) => setPrimary(color.toHexString())} />
<Divider />
<ConfigProvider
theme={{
token: {
colorPrimary: primary,
},
}}
>
<Space>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
</>
);
};
export default App;
중첩 테마 (Nested Theme)
ConfigProvider를 중첩하면 페이지의 일부 영역에만 로컬 테마를 적용할 수 있어요. 자식 테마에서 변경하지 않은 Design Token은 부모 테마를 이어받아요.
중첩 테마 (Nested Theme)
ConfigProvider를 중첩해서 로컬 테마 변경을 이룰 수 있어요. 하위 테마에서 변경하지 않은 Design Token은 상위 테마에서 상속받아요.
import React from 'react';
import { Button, ConfigProvider, Space } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
token: {
colorPrimary: '#1677ff',
},
}}
>
<Space>
<Button type="primary">Theme 1</Button>
<ConfigProvider
theme={{
token: {
colorPrimary: '#00b96b',
},
}}
>
<Button type="primary">Theme 2</Button>
</ConfigProvider>
</Space>
</ConfigProvider>
);
export default App;
Design Token 사용하기
현재 테마의 Design Token을 사용하고 싶다면 useToken 훅을 제공해서 Design Token을 얻을 수 있어요.
Design Token 사용하기
현재 테마의 Design Token을 사용하고 싶다면, useToken 훅을 제공해 그 값을 얻을 수 있어요.
import React from 'react';
import { theme } from 'antd';
const { useToken } = theme;
const App: React.FC = () => {
const { token } = useToken();
return (
<div
style={{
backgroundColor: token.colorPrimaryBg,
padding: token.padding,
borderRadius: token.borderRadius,
color: token.colorPrimaryText,
fontSize: token.fontSize,
}}
>
Use Design Token
</div>
);
};
export default App;
정적 사용 (예: less)
React 라이프사이클 밖에서 토큰이 필요하다면 정적 함수로 얻을 수 있어요:
import { theme } from 'antd';
const { getDesignToken } = theme;
const globalToken = getDesignToken();
ConfigProvider와 마찬가지로 getDesignToken도 theme처럼 설정 객체를 받을 수 있어요:
import type { ThemeConfig } from 'antd';
import { theme } from 'antd';
import { createRoot } from 'react-dom/client';
const { getDesignToken, useToken } = theme;
const config: ThemeConfig = {
token: {
colorPrimary: '#1890ff',
},
};
// By static function
const globalToken = getDesignToken(config);
// By hook
const App = () => {
const { token } = useToken();
return null;
};
// Example for rendering
createRoot(document.getElementById('#app')).render(
<ConfigProvider theme={config}>
<App />
</ConfigProvider>,
);
less 같은 전처리 스타일 프레임워크에서 사용하고 싶다면 less-loader로 주입하면 돼요:
{
loader: "less-loader",
options: {
lessOptions: {
modifyVars: mapToken,
},
},
}
호환 패키지는 v4 less 변수로 변환해 주는 변환 함수를 제공해요. 자세한 내용은 이 글을 읽어 보세요.
테마 에디터
사용자가 테마를 디버깅하는 데 도움이 되는 도구를 제공해요: Theme Editor
이 도구로 Design Token을 자유롭게 수정해서 원하는 테마를 만들 수 있어요.
Design Token
Design Token에서는 디자인에 더 적합한 3계층 구조를 제공하며, Design Token을 Seed Token, Map Token, Alias Token 세 부분으로 나눠요. 이 세 그룹은 단순한 묶음이 아니라 3계층 파생 관계예요. Map Token은 Seed Token에서 파생되고, Alias Token은 Map Token에서 파생돼요. 대부분의 경우 Seed Token만으로 커스텀 테마를 만들기에 충분해요. 하지만 더 높은 수준의 테마 커스터마이징이 필요하다면 antd에서 Design Token의 라이프사이클을 이해해야 해요.
Design Token의 생명주기
Seed Token
Seed Token은 모든 디자인 의도의 원점을 뜻해요. 예를 들어 colorPrimary를 바꾸면 테마 색을 바꿀 수 있고, antd 내부의 알고리즘이 Seed Token에 따라 일련의 대응 색상을 자동으로 계산해 적용해요:
const theme = {
token: {
colorPrimary: '#1890ff',
},
};
Map Token
Map Token은 Seed에서 파생된 그라데이션(gradient) 변수예요. 커스텀 Map Token은 theme.algorithm으로 구현하는 걸 권장해요. 그러면 Map Token 사이의 그라데이션 관계를 보장할 수 있어요. 물론 theme.token으로 덮어써서 일부 map 토큰 값을 개별적으로 수정할 수도 있어요.
const theme = {
token: {
colorPrimaryBg: '#e6f7ff',
},
};
Alias Token
Alias Token은 일부 공통 컴포넌트의 스타일을 일괄 제어하는 데 사용돼요. 기본적으로 Map Token의 별칭이거나, 특별히 처리된 Map Token이에요.
const theme = {
token: {
colorLink: '#1890ff',
},
};
Algorithm
기본 알고리즘은 Seed Token을 Map Token으로 확장하는 데 사용돼요. 예를 들어 기본 색에서 그라데이션 컬러 팔레트를 계산하거나, 기본 라운드 코너에서 다양한 크기의 라운드 코너를 계산하는 식이에요. 알고리즘은 단독으로 또는 임의로 조합해서 사용할 수 있어요. 예를 들어 dark와 compact 알고리즘을 조합하면 다크 앤 컴팩트 테마를 얻을 수 있어요.
import { theme } from 'antd';
const { darkAlgorithm, compactAlgorithm } = theme;
const theme = {
algorithm: [darkAlgorithm, compactAlgorithm],
};
API
Theme
| Property | Description | Type | Default | Version |
|---|---|---|---|---|
| token | Modify Design Token | AliasToken |
- | |
| inherit | Inherit theme configured in upper ConfigProvider | boolean | true | |
| algorithm | Modify the algorithms of theme | (token: SeedToken) => MapToken | ((token: SeedToken) => MapToken)[] |
defaultAlgorithm |
|
| components | Modify Component Token and Alias Token applied to components | ComponentsConfig |
- | |
| cssVar | CSS Variables Configuration | cssVar | - | |
| hashed | Style patch on the hash className | boolean | true | |
| zeroRuntime | Enable zero-runtime mode, which will not generate style at runtime, need to import additional CSS file | boolean | false | 6.0.0 |
ComponentsConfig
| Property | Description | Type | Default |
|---|---|---|---|
Component (Can be any antd Component name like Button) |
Modify Component Token or override Component used Alias Token | ComponentToken & AliasToken & { algorithm: boolean | (token: SeedToken) => MapToken | ((token: SeedToken) => MapToken)[]} |
- |
컴포넌트의
algorithm은 기본값이false라서 컴포넌트 토큰이 전역 토큰만 덮어써요.true로 설정하면 알고리즘이 전역과 동일해져요. 알고리즘이나 알고리즘 배열을 전달할 수도 있는데, 이 경우 전역 알고리즘을 덮어써요.
cssVar {#css-var}
| Property | Description | Type | Default | Version |
|---|---|---|---|---|
| prefix | Prefix of CSS variables, same as prefixCls configured on ConfigProvider by default |
string | ant |
|
| key | Unique key for current theme, filled with useId by default |
string | useId in React 18 |
SeedToken
MapToken
Inherit all SeedToken properties
AliasToken
Inherit all SeedToken and MapToken properties
FAQ
theme이 undefined에서 어떤 객체로, 또는 그 반대로 바뀌면 왜 컴포넌트가 다시 마운트되나요?
ConfigProvider에서는 DesignTokenContext로 컨텍스트를 전달해요. theme이 undefined면 Provider 계층이 설치되지 않아서 React VirtualDOM 구조가 처음부터, 또는 존재에서 없음으로 바뀌면서 컴포넌트가 다시 마운트돼요. 해결책: undefined 대신 빈 객체 {}를 사용하면 돼요.