How to Customize Ant Design Component Styles Using `classNames` and `styles` Props
Use the classNames and styles props available on every Ant Design v5+ component to override specific semantic slots (like root, item, or popup) with custom CSS classes or inline styles, merging your values with the library’s internal CSS-in-JS tokens via the useMergeSemantic hook.
Ant Design’s semantic style system, introduced in version 5.0.0, exposes two standardized props on all components that allow surgical customization without CSS specificity wars. According to the ant-design/ant-design source code, every major component—from Tree to Button to Select—implements this pattern through a centralized merging utility that combines global ConfigProvider values, prop-level overrides, and internal design tokens into final class names and style objects.
Understanding the Semantic Style API
The classNames and styles props accept either static objects or functions that return objects keyed by semantic slot names. Each component defines its own slots—such as root, list, listItem, or popup—through a SemanticClassNames type definition.
The classNames prop accepts Partial<Record<SemanticSlot, string>> or a function (info) => Partial<Record<SemanticSlot, string>>, allowing you to append or replace CSS classes for specific structural parts of a component. The styles prop follows the same pattern but accepts React.CSSProperties objects instead of strings, injecting inline styles that override the default theme tokens.
Both props support function-based values that receive runtime component state—such as disabled, loading, or open—enabling dynamic styling based on the component’s current condition.
The Core Merging Implementation
All customization flows through useMergeSemantic, implemented in components/_util/hooks/useMergeSemantic.ts at line 116. This hook standardizes how Ant Design reconciles three sources of style data:
- ConfigProvider context (
contextClassNames,contextStyles) for global theme overrides - Component props (
props.classNames,props.styles) for instance-specific customization - Internal defaults generated by the CSS-in-JS engine
The hook returns mergedClassNames and mergedStyles, which the component applies to its JSX elements. Because the merging runs once per render and results are memoized, there is no runtime performance penalty.
For example, in components/tree/Tree.tsx at line 245, the Tree component initializes the hook:
const [mergedClassNames, mergedStyles] = useMergeSemantic<
TreeProps,
TreeClassNamesType
>(props, {
classNames: contextClassNames,
styles: contextStyles,
});
The resulting mergedClassNames are applied to wrapper elements via clsx (see lines 321-323 in the same file), ensuring your custom classes coexist with Ant Design’s internal styling classes.
Semantic Slot Definitions
Each component exposes its available customization slots through a TypeScript definition. In components/tree/Tree.tsx (lines 117-131), the Tree component defines:
export type TreeSemanticClassNames = {
root?: string;
list?: string;
listItem?: string;
listItemIcon?: string;
// … additional slots
};
export type TreeClassNamesType = SemanticClassNamesType<TreeProps, TreeSemanticClassNames>;
Button, Upload, Select, and other components follow identical patterns. You can discover available slots for any component by searching for export type *SemanticClassNames in its source file.
Static Customization Examples
Customizing Tree Component Styles
Apply static class names and styles to specific structural parts of the Tree component:
import { Tree } from 'antd';
const customClassNames = {
root: 'my-tree-root', // targets the outermost wrapper
listItem: 'my-tree-item', // custom class for each node item
listItemIcon: 'my-tree-icon', // custom icon wrapper
};
const customStyles = {
root: { backgroundColor: '#f5f5f5' },
listItem: { padding: '6px 12px' },
};
<Tree
treeData={[/* … */]}
classNames={customClassNames}
styles={customStyles}
/>
As implemented in components/tree/Tree.tsx, these values merge with internal CSS-in-JS classes at render time, preserving Ant Design’s layout logic while applying your visual overrides.
Button State-Based Classes
For the Button component, defined in components/button/Button.tsx (lines 169-171), use a function to compute class names based on runtime state:
import { Button } from 'antd';
const classNamesFn = (info) => ({
// `info` contains component state: disabled, loading, type, etc.
root: info.disabled ? 'btn-disabled-custom' : 'btn-primary-custom',
icon: info.loading ? 'btn-spinning-icon' : undefined,
});
<Button type="primary" loading classNames={classNamesFn}>
Submit
</Button>
The hook merges the function return value exactly like a static object, applying your conditional classes to the button’s root and icon slots.
Dynamic and Contextual Customization
Function-Based Style Overrides
The Select component demonstrates dynamic style computation in components/select/index.tsx (lines 319-337). You can adjust styles based on the dropdown’s open state:
import { Select } from 'antd';
const styleFn = ({ open }) => ({
popup: {
borderColor: open ? '#1890ff' : '#d9d9d9',
boxShadow: open ? '0 4px 12px rgba(24, 144, 255, 0.15)' : 'none',
},
});
<Select
options={[{ label: 'A', value: 'a' }]}
classNames={{ popup: 'my-select-popup' }}
styles={styleFn}
open={true}
/>
The info parameter typically includes boolean flags representing component state, giving you precise control over visual feedback during interactions.
Global Theming with ConfigProvider
For application-wide customization, provide classNames and styles via ConfigProvider rather than individual props. The Upload component in components/upload/Upload.tsx (lines 347-349) reads these context values through useComponentConfig before merging:
import { ConfigProvider, Button, Upload } from 'antd';
const globalClassNames = {
Button: {
root: 'global-btn-root',
icon: 'global-btn-icon',
},
Upload: {
root: 'global-upload-wrapper',
list: 'global-upload-list',
},
};
<ConfigProvider component={{ classNames: globalClassNames }}>
<Button>Global Styled Button</Button>
<Upload>{/* … */}</Upload>
</ConfigProvider>
Context-level values are merged first, followed by component-level props, allowing both global themes and local exceptions to coexist.
How Style Merging Works
The merging algorithm follows a strict precedence hierarchy to resolve conflicts:
- ConfigProvider Context – Global
classNamesandstylesprovide the base layer - Component Props – User-provided values override context where keys overlap
- Internal Defaults – CSS-in-JS generated classes and token-based styles fill remaining slots
When both static objects and functions are provided, the hook executes functions with current component state, then performs a shallow merge of the resulting objects. Class names are concatenated using clsx, while style objects are spread together with later values winning property conflicts.
Summary
classNamesandstylesprops are available on all Ant Design v5+ components for semantic slot customization- The
useMergeSemantichook incomponents/_util/hooks/useMergeSemantic.tshandles merging of context, props, and internal defaults - Components define available slots via
SemanticClassNamesTypeScript types (e.g.,root,listItem,popup) - Both props accept static objects or functions that receive component state (
info) for dynamic styling - ConfigProvider supports global customization that individual components can override via their own props
- Merging occurs once per render with memoization, ensuring zero runtime performance impact
Frequently Asked Questions
What semantic slots are available for a specific Ant Design component?
Each component exports a SemanticClassNames type definition that lists all customizable slots. Search the component’s source file (e.g., components/button/Button.tsx) for export type *SemanticClassNames to see the full list of available keys like root, icon, popup, or listItem.
Can I use both classNames and styles on the same component?
Yes. Both props work independently and can be used simultaneously. The classNames prop appends or replaces CSS classes, while styles injects inline style objects. They merge through the same useMergeSemantic hook without interfering with each other, allowing you to combine external CSS files and dynamic inline styles.
Do function-based classNames receive the same info object as styles functions?
Yes. When providing a function to either prop, Ant Design passes an info object containing the component’s current state. For example, Button receives { disabled, loading, type }, while Select receives { open, disabled }. The exact properties vary by component and are typed in the component’s source definition.
Will customizing classNames break Ant Design’s default styling?
No. The merging logic in useMergeSemantic concatenates your custom classes with the library’s internal CSS-in-JS classes using clsx. Your classes augment rather than replace the default styling unless you explicitly override specific CSS properties with higher specificity in your own stylesheets.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →