Stop Overriding AntD CSS — Embrace antd zeroRuntime & Design Tokens
Hot take: your team's custom CSS overrides are often the real performance tax in Ant Design apps. They’re quick to write, but as the app grows they collide, cause unexpected re-mounts, and force runtime work that can be avoided. With Ant Design v6's antd zeroRuntime mode and Design Tokens, you can replace brittle selectors with a predictable, type-safe theming system and remove a chunk of runtime cost.
Why ad-hoc overrides hurt
The classic pattern looks like this:
/* styles.css */
.ant-btn { background: #1e88e5; }
It’s fast for a single page, but it scales poorly:
- Selectors collide as more overrides are added.
- Unscoped rules break encapsulation and make refactors expensive.
- Runtime hooks (like useStyleRegister/useCacheToken) still run per-render when components compute tokens or hashes — the app pays CPU even when visuals are static.
Ant Design v6 introduced a better option: antd zeroRuntime. Flip it on in ConfigProvider and shift style generation to build-time or to a static CSS bundle. Components will consume Design Tokens (global and component tokens) instead of relying on fragile class selectors.
What antd zeroRuntime gives you
- Disable runtime style generation: skip useStyleRegister and similar hooks on each render.
- Static CSS (or extracted per-component CSS): import
antd/dist/antd.cssor extract only the components you need with @ant-design/static-style-extract. - Predictable theming via Design Tokens and component tokens (e.g., colorPrimary, borderRadius, Button token overrides).
- Smaller CPU overhead during rendering and fewer surprising re-renders from style injection.
Quick example — stop overriding .ant-btn
Instead of overriding Button styles with a stylesheet, consume tokens and let the component be themed consistently.
import React from 'react';
import { ConfigProvider, theme, Button } from 'antd';
const App = () => (
<ConfigProvider theme={{ zeroRuntime: true }}>
<MyPage />
</ConfigProvider>
);
const MyPage = () => {
const { token } = theme.useToken();
return (
<div style={{ padding: token.paddingSM }}>
{/* Button will pick up colorPrimary from tokens */}
<Button type="primary">Primary</Button>
</div>
);
};
export default App;
If you need to customize Button-specific variables, use component tokens in ConfigProvider rather than global CSS overrides:
<ConfigProvider
theme={{
zeroRuntime: true,
token: { colorPrimary: '#1e88e5' },
components: { Button: { colorPrimary: '#1e88e5', borderRadius: 6 } }
}}
>
<App />
</ConfigProvider>
Migration checklist (practical, small steps)
1) Audit overrides
- Extract every custom selector (.ant-*, .my-override) into a list. Note why it exists (visual bug, one-off layout, etc.).
2) Map overrides to tokens
- For each override, decide whether it maps to a global token (colorPrimary, borderRadius), a component token (Button, Table), or a static layout style that belongs in your app CSS.
3) Enable antd zeroRuntime and bundle static styles
- Add at the correct root.
- For production builds, import
antd/dist/antd.cssor run @ant-design/static-style-extract to generate a smaller CSS file containing only the components you need.
4) Replace overrides incrementally
- Replace one UI area at a time and run visual diffs (Storybook snapshots or Percy) to catch regressions early. Keep the old overrides behind a feature flag until parity is verified.
Common migration pitfalls and how to avoid them
-
Token scope and provider placement
- Put ConfigProvider at the true root of the React tree for consistent tokens. If theme flips between undefined and an object, React may re-mount subtree components — prefer passing an empty object instead of undefined to avoid provider mount/unmount.
-
Third-party libs
- Libraries like @ant-design/x need to respect DesignTokenContext and propagate zeroRuntime. Upgrade these libs to versions that support zeroRuntime (many have PRs to forward the flag). If a library still injects runtime CSS, open an issue or vendor a patch until it’s updated.
-
Missing static styles in production
- zeroRuntime disables runtime injection — you must bundle static CSS. Options:
- Import
antd/dist/antd.cssfor full styles (easy, larger file). - Use
@ant-design/static-style-extractto generate component-only CSS for smaller outputs. - For custom prefixes or hashed classNames, generate a matching static stylesheet during build.
-
Message/Modal/Notification context gap
- AntD static methods (Modal.confirm, message) create their own root nodes and do not inherit ConfigProvider context. If you rely on tokens for those, you may need to wrap or patch how these utilities are created, or provide a global token via getDesignToken or by importing the static CSS that matches your theme.
-
Build-time responsibility
- zeroRuntime shifts work to build time: you’re responsible for ensuring the static CSS matches token-based semantics (prefix, hashes, included components). Factor this into CI and build scripts.
Tools & patterns that help
- @ant-design/static-style-extract — extract only needed CSS into a static file during build.
- theme.getDesignToken — compute tokens outside React lifecycle when you need token values in build scripts or server-side code.
- createStaticStyles / createStaticStylesFactory (from antd-style or similar) — for high-frequency-rendering components, create module-level static styles that reference CSS variables instead of running hooks every render.
Example using createStaticStyles (pseudocode):
// my-list.styles.ts (module-level)
import { createStaticStyles } from 'antd-style';
export const useListStyles = createStaticStyles(({ cssVar, css }) => ({
item: css`padding: ${cssVar.paddingSM}; border-radius: ${cssVar.borderRadius};`,
}));
This pattern avoids hook calls in hot-path render loops and simply uses CSS variables provided by your static stylesheet.
Is it worth it?
Yes, for most medium-to-large apps. The trade-off: more build-time configuration and careful migration vs. long-term runtime savings. Benefits you’ll notice:
- Measurable CPU wins on frequently rendered lists or pages.
- Smaller runtime overhead (fewer style hooks running on re-renders).
- Cleaner, type-safe theme surface for designers and devs to collaborate on.
- Fewer brittle selectors and easier refactors.
ZeroRuntime isn’t dogma — it’s a trade-off that pays off once your app size and complexity grow. Start with a targeted audit (buttons, tables, containers) and use the migration checklist above. If you hit an unexpected issue, it’s usually either a missing static CSS or a third-party library that needs an upgrade.
Have you tried antd zeroRuntime yet? If so, what unexpected issue popped up during your migration? Share your experiences in the comments — the ecosystem is still consolidating best practices, and your patterns will help others migrate smoothly.
Top comments (0)