테마 커스터마이징

테마 커스터마이징 (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를 사용하면 테마링 능력도 한층 강화되는데, 그 예는 다음과 같아요 (이것만 있는 건 아니에요):

  1. 테마를 동적으로 전환하기;
  2. 여러 테마 지원;
  3. 일부 컴포넌트에 대한 테마 변수 커스터마이징;
  4. ...

기본 사용법

테마에 영향을 주는 가장 작은 요소를 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의 생명주기

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 대신 빈 객체 {}를 사용하면 돼요.

더 알아보기 (Learn more)