Three hops, and nothing in the chain is hand-maintained.
A theme file of seeds and deviations compiles into a typed Ant Design ThemeConfig. The provider hands that config to antd, which emits every token as a CSS variable. antuikit.css maps those variables onto Tailwind theme keys, so a utility class and an antd component resolve to the same value.
The names at every hop are Ant Design's own token names, and each Tailwind theme variable maps to one of them, so there is no second vocabulary to keep in sync.
One theme is one file, and light and dark are modes inside it.
colorPrimary is the same in light and dark, so it is written once and cannot drift between two files. Multiple themes are still multiple files, one compiled module each.
Run tokens:build after editing it. It compiles the file into a typed module and checks every key against the installed Ant Design, so the valid keys follow antd upgrades.
A key antd does not publish is a warning, not a build failure. It still compiles into a typed, autocompleting token, so a designer can add a new variable in Figma without waiting on the kit.
Which one to reach for, and which one wins when two apply.
An alias deviation beats the algorithm's output for that key, and only that key. Seeds and aliases are resolved in one pass, so a stated colorBgLayout comes back unchanged while colorPrimaryHover is still derived from the seed.
The file stores what you decided, never what the algorithm computed.
Ant Design derives more than five hundred tokens from a few dozen seeds. A theme file holds the seeds plus the deviations you chose, and nothing else. A file of resolved values would be a snapshot that goes stale when antd's algorithm improves.
Because the file stores only your decisions, an Ant Design upgrade carries your theme forward.
Three independent axes, and the theme they paint with.
<AntuikitProvider> renders antd's <App> as its one element. That element carries the CSS variables antd generates, so every descendant inherits --ant-* from it, and App.useApp() gives you themed message, notification and modal.
All three axes are readable and settable from anywhere below it.
auto follows the operating system, so the setting and what is on screen are separate values.
Under auto the provider follows the operating system's prefers-color-scheme and keeps following it while the page is open. mode stays "auto" however that resolves, and isDark reports what it resolved to.
Render a theme picker from mode, because it is what the reader chose. Branch every visual decision on isDark, because it is what is on screen.
The provider mirrors the resolved mode onto <html>: the light or dark class, and color-scheme. It always does this, with no prop to turn it off. It themes the page canvas, including the area behind overscroll, and it is what Tailwind's dark: variant reads.
The one place theming needs help from outside React.
Client-rendered. Under auto the system preference is read synchronously during the first render, so everything inside the provider is correct on frame one. The <html> element is not: the theme class and color-scheme land in an effect, one frame late, which a dark-mode visitor sees as a white flash. A blocking inline script in <head> that stamps both before first paint removes it, and hands over to the provider rather than competing with it.
Server-rendered. A server cannot know the system preference, so under auto it renders light and a dark visitor's first paint is wrong until hydration. The script above fixes only the part that does not depend on antd's variable block, which is the canvas behind overscroll. The complete fix is to stop asking auto to guess: persist the choice in a cookie and pass it as defaultMode, and the server serializes the right variable block in the first response.
Next.js needs two more things, both of them documented on the Get Started page: suppressHydrationWarning on <html>, because the script mutates the element React hydrates, and <AntdRegistry>, so antd's styles are in that same first response with the layer wrapper intact.
A second axis that composes on top of whichever mode is active.
Compact changes sizing only: type sizes, line heights, control heights and part of antd's own size ramp. It touches no color and no radius. Your utilities read the same variables, so they go compact alongside antd's components, and a layout built from text-*, h-control-* and the token-bound padding steps tightens as one.
The padding steps that follow compact are p-xs, p-sm, p-base, p-md and p-lg. The outer ones, p-xxs, p-xl and p-xxl, stay the same, as they do in antd.
One setting that has to reach antd and the page at the same time.
antd's ConfigProvider takes a direction and that is all antd needs: it propagates through context and antd's components add their own right-to-left classes. It writes no dir attribute anywhere, though, and two things outside antd read the DOM instead: Tailwind's rtl: and ltr: variants, whose selectors key on the attribute, and the browser itself for bidi reordering, scrollbar placement and caret behavior.
So one setting has to reach ConfigProvider and <html dir> at the same time, and the provider is the single place that does both.
It does not set antd's locale, and it does not flip icons you place yourself. Add rtl:-scale-x-100 to a directional icon.
A dark panel inside a light app, without a second provider.
<ThemeScope> re-themes a subtree. It renders a <div> that carries the scope's variables, so antd components and your utilities re-scope together. It writes nothing to <html>, and it nests.
It follows the root's mode. To keep a region in one mode, pass the same config for light and dark.
Change one component without touching the rest.
Ant Design exposes a token set per component. Declare them in the theme file so the change travels with the theme, rather than in a provider prop at one call site.
A per-component token only exists once that component mounts, so it is not a value other parts of the system can read. Reach for a seed or an alias when more than one component should follow the change.
Every themed value has a utility class, and there are two traps to know.
antuikit.css is one @theme inline block that maps antd's variables onto Tailwind theme keys. Keep inline if you edit it. A plain @theme resolves the variables at the root, and a ThemeScope inside would be ignored.
Two traps to know before you write a class name:
The Tokens explorer lists every token by name, with its value in each mode.