ant-design/docs/react/customize-theme.en-US.md
kiner-tang(文辉) 8217330ec9
docs: update the docs about extract css (#41686)
* docs: update the docs about extract css

* docs: update the docs about extract css

* docs: update the docs about extract css

* docs: update the docs about extract css

* docs: update the docs about extract css

* docs: update the docs about extract css
2023-04-17 22:26:26 +08:00

18 KiB
Raw Blame History

order title
7 Customize Theme

Ant Design allows you to customize our design tokens to satisfy UI diversity from business or brand requirements, including primary color, border radius, border color, etc.

In version 5.0, we provide a new way to customize themes. Different from the less and CSS variables of the 4.x version, with CSS-in-JS, the ability of theming has also been enhanced, including but not limited to:

  1. Switching theme dynamically
  2. Multiple themes
  3. Customizing theme variables for some component
  4. ...

Customize theme with ConfigProvider

In version 5.0 we call the smallest element that affects the theme Design Token. By modifying the Design Token, we can present various themes or components.

Customize Design Token

You can pass theme to ConfigProvider to customize theme. After migrate to V5, theme of V5 will be applied by default. Here's a simple example:

import { Button, ConfigProvider } from 'antd';
import React from 'react';

const App: React.FC = () => (
  <ConfigProvider
    theme={{
      token: {
        colorPrimary: '#00b96b',
      },
    }}
  >
    <Button />
  </ConfigProvider>
);

export default App;

You will get a theme with primary color . And we can see the change in Button:

themed button

Use Preset Algorithms

Themes with different styles can be quickly generated by modifying algorithm. Ant Design 5.0 provides three sets of preset algorithms by default, which are default algorithm theme.defaultAlgorithm, dark algorithm theme.darkAlgorithm and compact algorithm theme.compactAlgorithm. You can switch algorithms by modifying the algorithm property of theme in ConfigProvider.

import { Button, ConfigProvider, theme } from 'antd';
import React from 'react';

const App: React.FC = () => (
  <ConfigProvider
    theme={{
      algorithm: theme.darkAlgorithm,
    }}
  >
    <Button />
  </ConfigProvider>
);

export default App;

Customize Component Token

In addition to Design Token, each component will also have its own Component Token to achieve style customization capabilities for components, and different components will not affect each other. Similarly, other Design Token of components can also be overridden in this way.

import { Checkbox, ConfigProvider, Radio } from 'antd';
import React from 'react';

const App: React.FC = () => (
  <ConfigProvider
    theme={{
      components: {
        Radio: {
          colorPrimary: '#00b96b',
        },
      },
    }}
  >
    <Radio>Radio</Radio>
    <Checkbox>Checkbox</Checkbox>
  </ConfigProvider>
);

export default App;

In this way, we changed the primary color of Radio to , and Checkbox is not affected.

component token

Notice: ConfigProvider will not take effect on static methods such as message.xxx, Modal.xxx, notification.xxx, because in these methods, antd will dynamically create new ones through ReactDOM.render React entities. Its context is not the same as the context of the current code, so context information cannot be obtained. When you need context information (such as the content configured by ConfigProvider), you can use the Modal.useModal method to return the modal entity and the contextHolder node. Just insert it where you need to get the context, or you can use App Component to simplify the problem of usingModal and other methods that need to manually implant the contextHolder.

Other Ways to Use Dynamic Themes

Switch Themes Dynamically

In v5, dynamically switching themes is very simple for users, you can dynamically switch themes at any time through the theme property of ConfigProvider without any additional configuration.

Local Theme

By nesting ConfigProvider you can apply local theme to some parts of your page. Design Tokens that have not been changed in the child theme will inherit the parent theme.

import { Button, ConfigProvider } from 'antd';
import React from 'react';

const App: React.FC = () => (
  <ConfigProvider
    theme={{
      token: {
        colorPrimary: '#1677ff',
      },
    }}
  >
    <Button />
    <ConfigProvider
      theme={{
        token: {
          colorPrimary: '#1890ff',
        },
      }}
    >
      <Button />
    </ConfigProvider>
  </ConfigProvider>
);

export default App;

Consume Design Token

If you want to consume the Design Token under the current theme, we provide useToken hook to get Design Token.

import { Button, theme } from 'antd';
import React from 'react';

const { useToken } = theme;

const App: React.FC = () => {
  const { token } = useToken();

  return <Button style={{ backgroundColor: token.colorPrimary }}>Button</Button>;
};

export default App;

Static consume (e.g. less)

When you need token out of React life cycle, you can use static function to get them:

import { theme } from 'antd';

const { defaultAlgorithm, defaultSeed } = theme;

const mapToken = defaultAlgorithm(defaultSeed);

If you want to use in preprocess style framework like less, use less-loader for injection:

{
  loader: "less-loader",
  options: {
    lessOptions: {
      modifyVars: mapToken,
    },
  },
}

Compatible package provide convert function to transform to v4 less variable. Read this for detail.

Advanced

In Design Token, we provide a three-layer structure that is more suitable for the design, and disassemble the Design Token into three parts: Seed Token, Map Token and Alias Token. These three groups of Tokens are not simple groupings, but a three-layer derivation relationship. Map Tokens are derived from Seed Tokens, and Alias Tokens are derived from Map Tokens. In most cases, using Seed Tokens is sufficient for custom themes. But if you need a higher degree of theme customization, you need to understand the life cycle of Design Token in antd.

Life Cycle of Design Token

token

Seed Token

Seed Token means the origin of all design intent. For example, we can change the theme color by changing colorPrimary, and the algorithm inside antd will automatically calculate and apply a series of corresponding colors according to the Seed Token:

const theme = {
  token: {
    colorPrimary: '#1890ff',
  },
};

Map Token

Map Token is a gradient variable derived from Seed. It is recommended to implement custom Map Token through theme.algorithm, which can ensure the gradient relationship between Map Tokens. It can also be overridden by theme.token to modify the value of some map tokens individually.

const theme = {
  token: {
    colorPrimaryBg: '#e6f7ff',
  },
};

Alias Token

Alias Token is used to control the style of some common components in batches, which is basically a Map Token alias, or a specially processed Map Token.

const theme = {
  token: {
    colorLink: '#1890ff',
  },
};

Algorithm

The basic algorithm is used to expand the Seed Token into a Map Token, such as calculating a gradient color palette from a basic color, or calculating rounded corners of various sizes from a basic rounded corner. Algorithms can be used alone or in any combination, for example, dark and compact algorithms can be combined to get a dark and compact theme.

import { theme } from 'antd';

const { darkAlgorithm, compactAlgorithm } = theme;

const theme = {
  algorithm: [darkAlgorithm, compactAlgorithm],
};

Legacy Browser Compatible

Please ref to CSS Compatible.

Server Side Render (SSR)

There are two options for server-side rendering styles, each with advantages and disadvantages:

  • Inline mode: there is no need to request additional style files during rendering. The advantage is to reduce additional network requests. The disadvantage is that the HTML volume will increase and the speed of the first screen rendering will be affected. Relevant discussion: #39891
  • Whole export: The antd component is pre-baked and styled as a css file to be introduced in the page. The advantage is that when opening any page, the same set of css files will be reused just like the traditional css scheme to hit the cache. The disadvantage is that if there are multiple themes in the page, additional baking is required

Inline mode

Use @ant-design/cssinjs to extract style:

import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
import { renderToString } from 'react-dom/server';

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>
`;
};

Whole export

If you want to detach a style file into a css file, try the following schemes:

  1. Installation dependency
npm install ts-node tslib cross-env --save-dev
  1. Add tsconfig.node.json
{
  "compilerOptions": {
    "strictNullChecks": true,
    "module": "NodeNext",
    "jsx": "react",
    "esModuleInterop": true
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}
  1. Add scripts/genAntdCss.tsx
// scripts/genAntdCss.tsx
import { extractStyle } from '@ant-design/static-style-extract';
import fs from 'fs';

const outputPath = './public/antd.min.css';

const css = extractStyle();

fs.writeFileSync(outputPath, css);

If you want to use mixed themes or custom themes, you can use the following script:

import { extractStyle } from '@ant-design/static-style-extract';
import { ConfigProvider } from 'antd';
import fs from 'fs';
import React from 'react';

const outputPath = './public/antd.min.css';

const testGreenColor = '#008000';
const testRedColor = '#ff0000';

const css = extractStyle((node) => (
  <>
    <ConfigProvider
      theme={{
        token: {
          colorBgBase: testGreenColor,
        },
      }}
    >
      {node}
    </ConfigProvider>
    <ConfigProvider
      theme={{
        token: {
          colorPrimary: testGreenColor,
        },
      }}
    >
      <ConfigProvider
        theme={{
          token: {
            colorBgBase: testRedColor,
          },
        }}
      >
        {node}
      </ConfigProvider>
    </ConfigProvider>
  </>
));

fs.writeFileSync(outputPath, css);

You can choose to execute this script before starting the development command or before compiling. Running this script will generate a full antd.min.css file directly in the specified directory of the current project (e.g. public).

Take Next.js for exampleexample

// package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "predev": "ts-node --project ./tsconfig.node.json ./scripts/genAntdCss.tsx",
    "prebuild": "cross-env NODE_ENV=production ts-node --project ./tsconfig.node.json ./scripts/genAntdCss.tsx"
  }
}

Then, you just need to import this file into the pages/_app.tsx file:

import { StyleProvider } from '@ant-design/cssinjs';
import type { AppProps } from 'next/app';
import '../public/antd.min.css';
import '../styles/globals.css'; // add this line

export default function App({ Component, pageProps }: AppProps) {
  return (
    <StyleProvider hashPriority="high">
      <Component {...pageProps} />
    </StyleProvider>
  );
}

Custom theme

If you're using a custom theme for your project, try baking in the following ways:

import { extractStyle } from '@ant-design/static-style-extract';
import { ConfigProvider } from 'antd';

const cssText = extractStyle((node) => (
  <ConfigProvider
    theme={{
      token: {
        colorPrimary: 'red',
      },
    }}
  >
    {node}
  </ConfigProvider>
));

Mixed theme

If you're using a mixed theme for your project, try baking in the following ways:

import { extractStyle } from '@ant-design/static-style-extract';
import { ConfigProvider } from 'antd';

const cssText = extractStyle((node) => (
  <>
    <ConfigProvider
      theme={{
        token: {
          colorBgBase: 'green ',
        },
      }}
    >
      {node}
    </ConfigProvider>
    <ConfigProvider
      theme={{
        token: {
          colorPrimary: 'blue',
        },
      }}
    >
      <ConfigProvider
        theme={{
          token: {
            colorBgBase: 'red ',
          },
        }}
      >
        {node}
      </ConfigProvider>
    </ConfigProvider>
  </>
));

More about static-style-extract, see static-style-extract.

Export the css files on demand

// scripts/genAntdCss.tsx
import { extractStyle } from '@ant-design/cssinjs';
import type Entity from '@ant-design/cssinjs/lib/Cache';
import { createHash } from 'crypto';
import fs from 'fs';
import path from 'path';

export type DoExtraStyleOptions = {
  cache: Entity;
  dir?: string;
  baseFileName?: string;
};
export function doExtraStyle({
  cache,
  dir = 'antd-output',
  baseFileName = 'antd.min',
}: DoExtraStyleOptions) {
  const baseDir = path.resolve(__dirname, '../../static/css');

  const outputCssPath = path.join(baseDir, dir);

  if (!fs.existsSync(outputCssPath)) {
    fs.mkdirSync(outputCssPath, { recursive: true });
  }

  const css = extractStyle(cache, true);
  if (!css) return '';

  const md5 = createHash('md5');
  const hash = md5.update(css).digest('hex');
  const fileName = `${baseFileName}.${hash.substring(0, 8)}.css`;
  const fullpath = path.join(outputCssPath, fileName);

  const res = `_next/static/css/${dir}/${fileName}`;

  if (fs.existsSync(fullpath)) return res;

  fs.writeFileSync(fullpath, css);

  return res;
}

Export on demand using the above tools in _document.tsx

// _document.tsx
import { StyleProvider, createCache } from '@ant-design/cssinjs';
import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document';
import { doExtraStyle } from '../scripts/genAntdCss';
export default class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const cache = createCache();
    let fileName = '';
    const originalRenderPage = ctx.renderPage;
    ctx.renderPage = () =>
      originalRenderPage({
        enhanceApp: (App) => (props) =>
          (
            <StyleProvider cache={cache}>
              <App {...props} />
            </StyleProvider>
          ),
      });

    const initialProps = await Document.getInitialProps(ctx);
    // 1.1 extract style which had been used
    fileName = doExtraStyle({
      cache,
    });
    return {
      ...initialProps,
      styles: (
        <>
          {initialProps.styles}
          {/* 1.2 inject css */}
          {fileName && <link rel="stylesheet" href={`/${fileName}`} />}
        </>
      ),
    };
  }

  render() {
    return (
      <Html lang="en">
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

See the demoExport the css files on demand demo

Shadow DOM Usage

Since <style /> tag insertion is different from normal DOM in Shadow DOM scenario, you need to use StyleProvider of @ant-design/cssinjs to configure the container property to set the insertion position:

import { StyleProvider } from '@ant-design/cssinjs';
import { createRoot } from 'react-dom/client';

const shadowRoot = someEle.attachShadow({ mode: 'open' });
const container = document.createElement('div');
shadowRoot.appendChild(container);
const root = createRoot(container);

root.render(
  <StyleProvider container={shadowRoot}>
    <MyApp />
  </StyleProvider>,
);

API

Theme

Property Description Type Default
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 OverrideToken -

OverrideToken

Property Description Type Default
Component (Can be any antd Component name like Button) Modify Component Token or override Component used Alias Token ComponentToken & AliasToken -

SeedToken

MapToken

Inherit all SeedToken properties

AliasToken

Inherit all SeedToken and MapToken properties

StyleProvider

Please ref @ant-design/cssinjs.

How to Debug your Theme

We provide tools to help users debug themes: Theme Editor

You can use this tool to freely modify Design Token to meet your theme expectations.

Theme Presets

FAQ

Why component re-mounted when theme changed from undefined to some object or to undefined?

In ConfigProvider, we pass context through DesignTokenContext. When theme is undefined, a layer of Provider will not be set, so React VirtualDOM structure changes from scratch or from existence to nothing, causing components to be re-mounted. Solution: Replace undefined with an empty object {}.