
Web Ui Ant Design
- 7 installs
- 19 repo stars
- Updated July 19, 2026
- agents-inc/skills
web-ui-ant-design is a Claude Code skill for building enterprise React UIs with the Ant Design component library, ConfigProvider theming, and design tokens.
About
web-ui-ant-design is a Claude Code skill for building React UIs with the Ant Design enterprise component library. A developer uses it to theme via ConfigProvider and design tokens, build data tables and validated forms, and use Pro Components for admin panels. It matters for data-heavy dashboards that need a complete component set out of the box.
- Ant Design enterprise React components (60+) with ConfigProvider theming
- Three-layer design token system (Seed, Map, Alias) and dark-mode algorithms
- Type-safe forms (Form.useForm), data tables, and Pro Components; targets antd v6
Web Ui Ant Design by the numbers
- 7 all-time installs (skills.sh)
- Ranked #1,761 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
web-ui-ant-design capabilities & compatibility
- Capabilities
- component library usage · ui theming · data tables · form building
- Use cases
- frontend · ui design
What web-ui-ant-design says it does
Ant Design is an enterprise-grade React UI library providing a complete set of high-quality components.
You MUST wrap your app with ConfigProvider for theming and locale - never override component styles with global CSS
npx skills add https://github.com/agents-inc/skills --skill web-ui-ant-designAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 7 |
|---|---|
| repo stars | ★ 19 |
| Last updated | July 19, 2026 |
| Repository | agents-inc/skills ↗ |
What it does
Build enterprise React admin panels and dashboards with Ant Design components, ConfigProvider theming, tables, and forms.
Who is it for?
Enterprise admin panels, dashboards, and data-heavy React apps needing a complete component set with theming.
Skip if: Building a custom design system from scratch, minimal-bundle marketing sites, wanting full styling control, or non-React apps.
When should I use this skill?
Building React UIs with Ant Design (ConfigProvider theming, Table, Form, Layout, enterprise patterns).
What you get
React admin UIs are built from Ant Design's component set with token-based theming and type-safe forms and tables.
- Themed Ant Design UI
- Data tables
- Validated forms
By the numbers
- 60+ components covering layout, data display, data entry, navigation, feedback
- Three-layer token architecture (Seed > Map > Alias)
Files
Ant Design Patterns
Quick Guide: Ant Design is an enterprise-grade React UI library providing a complete set of high-quality components. Use ConfigProvider with design tokens for theming, the three-layer token system (Seed, Map, Alias) for customization, and the App component for context-aware feedback methods. Current: v6.x (pure CSS variables by default, zero-runtime mode, React 18+ required). v5.x is in maintenance. All patterns in this skill apply to both v5 and v6 unless noted.
---
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)(You MUST wrap your app with ConfigProvider for theming and locale - never override component styles with global CSS)
(You MUST use the App component and useApp() hook for message/notification/modal - never use static methods directly as they cannot consume ConfigProvider context)
(You MUST use Form.useForm() with TypeScript generics for type-safe form handling - never use untyped form instances)
(You MUST use CSS variables mode (cssVar: true) for optimal theme-switching performance in production)
</critical_requirements>
---
Auto-detection: Ant Design, antd, ConfigProvider, theme.defaultAlgorithm, theme.darkAlgorithm, theme.compactAlgorithm, useToken, Form.useForm, Form.List, Form.useWatch, ProTable, ProForm, ProLayout, @ant-design/icons, @ant-design/pro-components, AntdRegistry, Table columns, message.success, notification.open, Modal.confirm
When to use:
- Building enterprise admin panels, dashboards, and data-heavy applications
- Need a comprehensive component library with consistent design language out of the box
- Working with complex data tables, forms with validation, and multi-step workflows
- Requiring built-in internationalization, dark mode, and theme customization
When NOT to use:
- Building a custom design system from scratch (use headless primitives)
- Need minimal bundle size for a simple marketing site (Ant Design is large)
- Want full control over styling without design opinions (use unstyled primitives)
- Building non-React applications (Ant Design is React-specific)
Key patterns covered:
- ConfigProvider theming with design tokens (Seed, Map, Alias, Component tokens)
- Table with sorting, filtering, virtual scrolling, and custom rendering
- Form with validation, dynamic fields (Form.List), and TypeScript generics
- Layout system (Layout, Grid, Space, Flex)
- Feedback patterns (Modal, Message, Notification via App/useApp)
- Dark mode and theme switching with algorithms
- Next.js SSR with AntdRegistry
- Pro Components (ProTable, ProForm, ProLayout)
---
Examples
- Core Setup & Theming -- ConfigProvider, App wrapper, design tokens, dark mode, nested themes, useToken
- Forms & Validation -- Form, Form.Item, validation rules, useForm, Form.List, Form.useWatch, modal form
- Tables -- Table, columns, sorting, filtering, pagination, row selection, virtual scrolling, expandable rows
- Layout -- Layout, Sider, Header, Content, Grid (Row/Col), Space, Flex
- Feedback Components -- Modal, Drawer, message, notification, useApp
- Data Display -- Card, Descriptions, Statistic, Tag, Badge
- Navigation & Icons -- Menu, Breadcrumb, icon tree-shaking, custom icons
- Pro Components -- ProLayout, ProTable, ProForm, StepsForm
- Next.js Integration -- AntdRegistry, SSR, client components, App Router
- Internationalization -- ConfigProvider locale, dayjs locale sync
For quick reference and component checklists, see reference.md.
---
<philosophy>
Philosophy
Ant Design follows the principles of Natural, Certain, Meaningful, and Growing to provide an enterprise-grade design system. It solves UI consistency across large teams by providing:
- Complete component set: 60+ components covering layout, data display, data entry, navigation, and feedback
- Design token system: Three-layer architecture (Seed > Map > Alias) enabling systematic customization without CSS overrides
- Enterprise patterns: Built-in pagination, filtering, form validation, internationalization, and accessibility
Architecture (CSS-in-JS with CSS Variables): Ant Design uses a CSS-in-JS engine (@ant-design/cssinjs) with design tokens. v6 defaults to pure CSS Variables mode for reduced bundle size and instant theme switching. v6 also supports zero-runtime mode (zeroRuntime: true) where styles are pre-extracted to static CSS. Tree-shaking is built-in -- no babel-plugin-import needed.
When to use Ant Design:
- Enterprise admin interfaces with data tables, forms, and dashboards
- Internal tools where development speed matters more than unique design
- Projects needing i18n, RTL, and accessibility out of the box
- Teams wanting a comprehensive, well-documented component library
When NOT to use:
- Consumer-facing products needing distinctive brand design (too opinionated)
- Performance-critical SPAs where bundle size must be minimal
- Projects using a utility-class-first styling paradigm
</philosophy>
---
<patterns>
Core Patterns
Pattern 1: App Root Setup
The minimal app setup wraps everything in ConfigProvider + App:
import { ConfigProvider, App as AntApp } from "antd";
import type { ThemeConfig } from "antd";
import enUS from "antd/locale/en_US";
const THEME_CONFIG: ThemeConfig = {
cssVar: true,
token: { colorPrimary: "#1677ff", borderRadius: 6 },
};
function App() {
return (
<ConfigProvider theme={THEME_CONFIG} locale={enUS}>
<AntApp>
<MainContent />
</AntApp>
</ConfigProvider>
);
}
export { App };Why this structure: ConfigProvider provides theme tokens and locale to all children. App component enables context-aware message/notification/modal APIs. cssVar mode optimizes theme switching performance.
See examples/core.md for enterprise theme, dark mode toggle, nested themes, and useToken patterns.
---
Pattern 2: Design Tokens and Theming
Ant Design uses a three-layer token system:
- Seed Tokens: Foundational values (
colorPrimary,fontSize,borderRadius) that derive all other tokens - Map Tokens: Derived from seed tokens via algorithms (
colorPrimaryBg,colorPrimaryHover) - Alias Tokens: Semantic tokens mapping to use cases (
colorBgContainer,colorTextHeading) - Component Tokens: Per-component overrides (
Button.primaryShadow,Table.headerBg)
// Access tokens programmatically for custom components
import { theme } from "antd";
const { useToken } = theme;
function CustomCard() {
const { token } = useToken();
return (
<div
style={{ background: token.colorBgContainer, padding: token.paddingLG }}
>
Styled with design tokens
</div>
);
}See examples/core.md for full theme configuration, nested themes, and StatusCard using useToken.
---
Pattern 3: Dark Mode and Theme Switching
import { ConfigProvider, theme as antTheme } from "antd";
// Switch between algorithms for dark/light/compact modes
const themeConfig = {
cssVar: true,
algorithm: isDark ? antTheme.darkAlgorithm : antTheme.defaultAlgorithm,
token: { colorPrimary: "#1677ff" },
};
// Combine algorithms: dark + compact
const combined = {
algorithm: [antTheme.darkAlgorithm, antTheme.compactAlgorithm],
};See examples/core.md for dark mode toggle with persistence and algorithm combining.
---
Pattern 4: Layout System
Use Layout for page-level structure, Grid (Row/Col) for responsive content areas, Flex for inline element alignment, Space for uniform gaps between small elements.
// Page shell: Layout + Sider + Header + Content
<Layout style={{ minHeight: "100vh" }}>
<Sider width={200} collapsible>
<Menu theme="dark" mode="inline" items={MENU_ITEMS} />
</Sider>
<Layout>
<Header />
<Content>{children}</Content>
</Layout>
</Layout>
// Responsive grid: Row + Col (24-column)
<Row gutter={[16, 16]}>
<Col xs={24} md={8}><Card /></Col>
<Col xs={24} md={8}><Card /></Col>
</Row>
// Flex alignment (v5.10+)
<Flex gap={8} justify="space-between" align="center" wrap>
<Button type="primary">Save</Button>
<Button>Cancel</Button>
</Flex>See examples/layout.md for full layout, grid, and flex examples.
---
Pattern 5: Table
Key requirements: TypeScript generics on Table<T> and ColumnsType<T>, rowKey prop always set, typed onChange handler.
import { Table } from "antd";
import type { ColumnsType } from "antd/es/table";
// Virtual scrolling (10,000+ rows): requires both scroll.x and scroll.y as numbers
<Table<DataRecord>
virtual
scroll={{ x: 1200, y: 500 }}
columns={columns} // All columns need explicit width
pagination={false}
/>;See examples/table.md for server-side table, expandable rows, summary rows, and virtual scrolling.
---
Pattern 6: Form with Validation and Dynamic Fields
Key requirements: Form.useForm<T>() with TypeScript generic, initialValues on Form (not Form.Item), htmlType="submit" on submit button, destroyOnClose on Modal/Drawer containing Form.
const [form] = Form.useForm<MyFormValues>();
const watchedValue = Form.useWatch("fieldName", form);
<Form<MyFormValues>
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{ role: "viewer" }}
>
<Form.Item name="email" rules={[{ required: true, type: "email" }]}>
<Input />
</Form.Item>
</Form>;See examples/form.md for complex validation, Form.List dynamic fields, Form.useWatch, and modal form patterns.
---
Pattern 7: Feedback Components (Modal, Message, Notification)
Always use App.useApp() for feedback -- never static methods:
function MyComponent() {
const { message, notification, modal } = App.useApp();
// These respect ConfigProvider theme and locale
message.success("Saved!");
notification.open({ message: "Update", description: "..." });
modal.confirm({
title: "Delete?",
onOk: async () => {
/* ... */
},
});
}See examples/feedback.md for declarative Modal, Drawer, and Popconfirm patterns.
---
Pattern 8: Data Display Components
// Descriptions for detail views
<Descriptions bordered column={{ xs: 1, sm: 2, md: 3 }} items={details} />
// Statistic cards for dashboards
<Statistic title="Revenue" value={112893} prefix="$" precision={2} />See examples/data-display.md for Descriptions, Statistic, Card grid, Tag, and Badge patterns.
---
Pattern 9: Navigation and Icons
// Use items API (v4.20+) - not JSX children
<Menu mode="inline" items={MENU_ITEMS} onClick={({ key }) => navigate(key)} />;
// Icons: always import individually for tree-shaking
import { UserOutlined } from "@ant-design/icons";
// NEVER: import * as Icons from "@ant-design/icons" (500KB+)See examples/navigation.md for Menu, Breadcrumb, icon tree-shaking, and custom SVG icons.
---
Pattern 10: Next.js App Router Integration
// app/layout.tsx - wrapping order matters: AntdRegistry > ConfigProvider > App
<AntdRegistry>
<ConfigProvider theme={THEME} locale={enUS}>
<AntApp>{children}</AntApp>
</ConfigProvider>
</AntdRegistry>See examples/nextjs.md for SSR setup, client components, and sub-component workarounds.
---
Pattern 11: Internationalization
ConfigProvider locale handles antd component text. Set dayjs locale separately for date/time formatting.
See examples/i18n.md for locale switching with dayjs sync.
---
Pattern 12: Pro Components
ProTable, ProForm, and ProLayout provide page-level enterprise abstractions with auto-generated search forms, step wizards, and route-based menus.
See examples/pro-components.md for ProLayout, ProTable CRUD, and StepsForm patterns.
</patterns>
---
<performance>
Performance Optimization
CSS Variables Mode
// v6: CSS variables are default. v5: opt in with cssVar: true
const THEME: ThemeConfig = {
cssVar: true,
hashed: false, // Disable hash when only one antd version in the app
};
// v6 zero-runtime mode: no runtime style generation
// Import 'antd/dist/antd.css' for default styles, or use
// @ant-design/static-style-extract for custom themes
const ZERO_RUNTIME_THEME: ThemeConfig = {
zeroRuntime: true,
};CSS variables mode eliminates runtime style recalculation when switching themes. hashed: false is safe when only one antd version exists. Zero-runtime mode (v6) completely removes runtime style generation for maximum performance.
Tree-Shaking
Tree-shaking works natively -- no babel-plugin-import needed. Icons must always be imported individually (import { UserOutlined } from "@ant-design/icons") or via path imports.
Virtual Scrolling
// Table: virtual requires both scroll.x and scroll.y as numbers
<Table virtual scroll={{ x: 1200, y: 500 }} />
// Select: virtual prop for large option lists
<Select virtual options={largeOptionsList} />
// Tree/TreeSelect: virtual prop
<Tree virtual treeData={largeTreeData} /></performance>
---
<decision_framework>
Decision Framework
Choosing Feedback Components
Need to show user feedback?
├─ Brief status update (success/error/loading) -> message via useApp()
├─ Detailed notification with title + description -> notification via useApp()
├─ Requires user decision -> modal.confirm() via useApp()
├─ Complex form or content -> Modal component (declarative, with open prop)
└─ Side panel with content -> Drawer componentTable vs ProTable
Building a data table?
├─ Simple display with basic sort/filter -> Table
├─ Need auto-generated search form -> ProTable
├─ Need server-side pagination + filtering -> ProTable (request API)
├─ Custom complex UI around table -> Table (more control)
└─ CRUD page with toolbar actions -> ProTable (toolBarRender)Form vs ProForm
Building a form?
├─ Simple single-page form -> Form
├─ Multi-step wizard -> StepsForm (from ProForm)
├─ Form in modal -> ModalForm (from ProForm)
├─ Form in drawer -> DrawerForm (from ProForm)
├─ Search/filter form -> QueryFilter or LightFilter (from ProForm)
└─ Need full layout control -> Form (more flexible)Layout Approach
How to lay out content?
├─ Page-level shell (sidebar + header + content) -> Layout
├─ Responsive grid of cards/panels -> Row + Col (24-column grid)
├─ Flex alignment of inline elements -> Flex (v5.10+)
├─ Uniform spacing between small elements -> Space
├─ Enterprise admin with route-based menu -> ProLayout
└─ Responsive breakpoints needed -> Row + Col with xs/sm/md/lg/xl propsTheming Approach
How to customize appearance?
├─ Brand colors only -> Seed tokens (colorPrimary, etc.)
├─ Specific component tweaks -> Component tokens (Button.colorPrimary)
├─ Dark mode -> algorithm: theme.darkAlgorithm
├─ Compact spacing -> algorithm: theme.compactAlgorithm
├─ Section-specific theme -> Nested ConfigProvider
├─ Access tokens in custom components -> useToken() hook
└─ Dynamic theme switching -> cssVar: true + state-driven algorithm</decision_framework>
---
<integration>
Integration Guide
Routing: Layout, Menu, and Breadcrumb components accept onClick / href handlers -- wire them to your router's navigation. Menu items array maps naturally to route definitions.
Data fetching: Table and ProTable work with any data source. Pass fetched data via dataSource prop or use ProTable's request callback which expects { data, success, total }.
Date library: dayjs is the default date library (replaces moment.js from v4). Date components use it internally -- set dayjs locale separately from ConfigProvider locale.
Ant Design ecosystem packages:
@ant-design/pro-components-- enterprise patterns (ProTable, ProForm, ProLayout)@ant-design/nextjs-registry-- SSR style extraction for SSR frameworks@ant-design/icons-- icon library (import individually for tree-shaking)
Styling coexistence: Avoid overriding antd styles with global CSS -- use design tokens and component tokens instead. Antd's CSS-in-JS styles have their own specificity; mixing with utility-class frameworks requires careful management.
</integration>
---
<red_flags>
RED FLAGS
High Priority Issues:
- Using static
message.success(),notification.open(),Modal.confirm()without App wrapper -- they bypass ConfigProvider context, leading to wrong theme and broken locale - Overriding antd styles with global CSS (
.ant-btn { ... }) -- breaks on theme changes and version upgrades, use component tokens instead - Importing entire icon set (
import * as Icons from "@ant-design/icons") -- adds 500KB+ to bundle - Using
value/defaultValueon form controls insideForm.Itemwithname-- conflicts with Form's state management
Medium Priority Issues:
- Missing
rowKeyon Table -- causes React key warnings and update bugs - Using
Form.Item initialValueinstead ofForm initialValues-- inconsistent behavior with form reset - Not wrapping app with
<App>component when using feedback methods -- feedback renders outside theme context - Missing
destroyOnCloseon Modal with Form inside -- stale form state persists across open/close
Common Mistakes:
- Forgetting to set dayjs locale alongside ConfigProvider locale (date components show wrong language)
- Using dot-notation sub-components in Next.js App Router server components (
<Select.Option>) - Not enabling
cssVar: truefor apps that switch themes (causes full style recalculation) - Wrapping AntdRegistry inside ConfigProvider instead of outside in Next.js (breaks style extraction)
Gotchas & Edge Cases:
Form.useWatchtriggers component re-render -- use sparingly in performance-sensitive forms- Table
onChangefires for pagination, filters, AND sorting -- check the extra parameter to determine which changed Modal.confirm()returns a reference for updating/destroying -- store it if you need to close programmatically- ConfigProvider
theme.componentstokens withalgorithm: truederive from the component'scolorPrimary, not the global one - Virtual Table requires explicit column
widthvalues and bothscroll.xandscroll.yas numbers -- without them, columns collapse or virtual mode fails - ProTable
requestmust return{ data, success, total }-- missingsuccess: truecauses infinite loading - Nested ConfigProvider inherits unset tokens from parent -- set tokens explicitly if you want isolation
@ant-design/icons@6is NOT compatible withantd@5-- always upgrade both packages together
</red_flags>
---
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)(You MUST wrap your app with ConfigProvider for theming and locale - never override component styles with global CSS)
(You MUST use the App component and useApp() hook for message/notification/modal - never use static methods directly as they cannot consume ConfigProvider context)
(You MUST use Form.useForm() with TypeScript generics for type-safe form handling - never use untyped form instances)
(You MUST use CSS variables mode (cssVar: true) for optimal theme-switching performance in production)
Failure to follow these rules will cause theme inconsistencies, broken internationalization, and degraded performance.
</critical_reminders>
Ant Design -- Core Setup & Theming Examples
Core setup, theme configuration, design tokens, dark mode, and ConfigProvider patterns. See SKILL.md for core concepts.
Related examples:
- Forms & Validation
- Tables & Data Display
- Feedback Components
- Next.js Integration
---
Complete Enterprise Theme
import { ConfigProvider, App as AntApp } from "antd";
import type { ThemeConfig } from "antd";
import { theme } from "antd";
import enUS from "antd/locale/en_US";
const BRAND_PRIMARY = "#2563eb";
const BRAND_SUCCESS = "#16a34a";
const BRAND_WARNING = "#ea580c";
const BRAND_ERROR = "#dc2626";
const BRAND_BORDER_RADIUS = 8;
const BRAND_FONT_SIZE = 14;
const BRAND_FONT_FAMILY =
"'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
const ENTERPRISE_THEME: ThemeConfig = {
cssVar: true,
hashed: false, // Safe when only one antd version in the app
token: {
colorPrimary: BRAND_PRIMARY,
colorSuccess: BRAND_SUCCESS,
colorWarning: BRAND_WARNING,
colorError: BRAND_ERROR,
borderRadius: BRAND_BORDER_RADIUS,
fontSize: BRAND_FONT_SIZE,
fontFamily: BRAND_FONT_FAMILY,
colorBgLayout: "#f5f5f5",
},
components: {
Button: {
controlHeight: 36,
algorithm: true,
},
Table: {
headerBg: "#fafafa",
headerColor: "#1f2937",
rowHoverBg: "#eff6ff",
headerSortActiveBg: "#e5e7eb",
},
Card: {
headerFontSize: 16,
},
Menu: {
itemHeight: 44,
subMenuItemBg: "transparent",
},
Form: {
labelFontSize: 14,
verticalLabelPadding: "0 0 4px",
},
Input: {
controlHeight: 36,
},
Select: {
controlHeight: 36,
},
},
};
function EnterpriseApp() {
return (
<ConfigProvider theme={ENTERPRISE_THEME} locale={enUS}>
<AntApp>
<AppRoutes />
</AntApp>
</ConfigProvider>
);
}
export { EnterpriseApp, ENTERPRISE_THEME };---
Dark Mode Toggle with Persistence
import { useState, useEffect, useCallback } from "react";
import { ConfigProvider, App as AntApp, Switch, theme as antTheme } from "antd";
import type { ThemeConfig } from "antd";
import { SunOutlined, MoonOutlined } from "@ant-design/icons";
const STORAGE_KEY = "app-theme-mode";
const BRAND_PRIMARY = "#2563eb";
function useThemeMode() {
const [isDark, setIsDark] = useState(() => {
if (typeof window === "undefined") return false;
return localStorage.getItem(STORAGE_KEY) === "dark";
});
useEffect(() => {
localStorage.setItem(STORAGE_KEY, isDark ? "dark" : "light");
// Optionally set data attribute for non-antd elements
document.documentElement.setAttribute(
"data-theme",
isDark ? "dark" : "light",
);
}, [isDark]);
const toggle = useCallback(() => setIsDark((prev) => !prev), []);
return { isDark, toggle } as const;
}
function DarkModeApp() {
const { isDark, toggle } = useThemeMode();
const themeConfig: ThemeConfig = {
cssVar: true,
algorithm: isDark ? antTheme.darkAlgorithm : antTheme.defaultAlgorithm,
token: {
colorPrimary: BRAND_PRIMARY,
},
};
return (
<ConfigProvider theme={themeConfig}>
<AntApp>
<div style={{ padding: 24 }}>
<Switch
checked={isDark}
onChange={toggle}
checkedChildren={<MoonOutlined />}
unCheckedChildren={<SunOutlined />}
/>
<MainContent />
</div>
</AntApp>
</ConfigProvider>
);
}
export { DarkModeApp, useThemeMode };---
Combining Algorithms (Dark + Compact)
import { ConfigProvider, theme } from "antd";
// Dark + Compact combined
const DARK_COMPACT_THEME = {
cssVar: true,
algorithm: [theme.darkAlgorithm, theme.compactAlgorithm],
token: {
colorPrimary: "#1677ff",
},
};
function DarkCompactApp() {
return (
<ConfigProvider theme={DARK_COMPACT_THEME}>
<MainContent />
</ConfigProvider>
);
}
export { DarkCompactApp };When to use: Data-dense dashboards benefit from compact + dark. Algorithms can be combined in any order.
---
Nested Themes
import { ConfigProvider, Card, Button } from "antd";
function NestedThemeExample() {
return (
<ConfigProvider theme={{ token: { colorPrimary: "#1677ff" } }}>
<Card title="Default Theme">
<Button type="primary">Blue Button</Button>
{/* Nested theme overrides only colorPrimary, inherits everything else */}
<ConfigProvider theme={{ token: { colorPrimary: "#eb2f96" } }}>
<Card title="Pink Theme Section">
<Button type="primary">Pink Button</Button>
</Card>
</ConfigProvider>
</Card>
</ConfigProvider>
);
}
export { NestedThemeExample };When to use: Multi-brand sections within a single page, embedded widgets needing distinct themes, component library previews.
---
Using useToken for Custom Components
import { theme, Card } from "antd";
const { useToken } = theme;
function StatusCard({
status,
title,
description,
}: {
status: "success" | "error" | "warning" | "info";
title: string;
description: string;
}) {
const { token } = useToken();
const STATUS_COLORS = {
success: {
bg: token.colorSuccessBg,
border: token.colorSuccess,
text: token.colorSuccessText,
},
error: {
bg: token.colorErrorBg,
border: token.colorError,
text: token.colorErrorText,
},
warning: {
bg: token.colorWarningBg,
border: token.colorWarning,
text: token.colorWarningText,
},
info: {
bg: token.colorInfoBg,
border: token.colorInfo,
text: token.colorInfoText,
},
} as const;
const colors = STATUS_COLORS[status];
return (
<Card
style={{
backgroundColor: colors.bg,
borderColor: colors.border,
borderWidth: token.lineWidth,
borderStyle: token.lineType,
borderRadius: token.borderRadiusLG,
}}
>
<h3 style={{ color: colors.text, margin: 0, fontSize: token.fontSizeLG }}>
{title}
</h3>
<p
style={{
color: token.colorTextSecondary,
margin: `${token.marginXS}px 0 0`,
}}
>
{description}
</p>
</Card>
);
}
export { StatusCard };Why good: useToken reads current ConfigProvider context, ensuring custom elements match the active theme including dark mode.
Ant Design -- Data Display Examples
Card, Descriptions, Statistic, Tag, and Badge patterns. See SKILL.md for core concepts.
Related examples:
- Tables & Data Display
- Layout
- Core Setup & Theming
---
Descriptions (Detail View)
import { Descriptions, Badge } from "antd";
import type { DescriptionsProps } from "antd";
const USER_DETAILS: DescriptionsProps["items"] = [
{ key: "name", label: "Name", children: "John Doe" },
{ key: "email", label: "Email", children: "john@example.com" },
{ key: "phone", label: "Phone", children: "+1 234 567 890" },
{ key: "role", label: "Role", children: "Administrator" },
{
key: "status",
label: "Status",
children: <Badge status="success" text="Active" />,
},
];
function UserDetail() {
return (
<Descriptions
title="User Information"
bordered
column={{ xs: 1, sm: 2, md: 3 }}
items={USER_DETAILS}
/>
);
}
export { UserDetail };---
Dashboard Stats Cards
import { Card, Row, Col, Statistic } from "antd";
import { ArrowUpOutlined, ArrowDownOutlined } from "@ant-design/icons";
const PRECISION = 2;
function DashboardCards() {
return (
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} lg={6}>
<Card>
<Statistic
title="Revenue"
value={112893}
precision={PRECISION}
prefix="$"
valueStyle={{ color: "#3f8600" }}
suffix={<ArrowUpOutlined />}
/>
</Card>
</Col>
<Col xs={24} sm={12} lg={6}>
<Card>
<Statistic
title="Active Users"
value={9280}
valueStyle={{ color: "#cf1322" }}
suffix={<ArrowDownOutlined />}
/>
</Card>
</Col>
</Row>
);
}
export { DashboardCards };Ant Design -- Feedback Components Examples
Modal, Drawer, message, notification, and Popconfirm patterns using App.useApp(). See SKILL.md for core concepts.
Related examples:
- Forms & Validation
- Core Setup & Theming
- Tables & Data Display
---
Using App Component and useApp Hook
import { App, Button, Space } from "antd";
// Wrap your application root with <App> component
function FeedbackDemo() {
const { message, notification, modal } = App.useApp();
const showMessage = () => {
message.success("Operation completed successfully");
};
const showNotification = () => {
notification.open({
message: "New Update Available",
description: "Version 2.0 is ready to install.",
placement: "topRight",
});
};
const showConfirm = () => {
modal.confirm({
title: "Delete this item?",
content: "This action cannot be undone.",
okText: "Delete",
okType: "danger",
cancelText: "Cancel",
onOk: async () => {
await deleteItem();
message.success("Item deleted");
},
});
};
return (
<Space>
<Button onClick={showMessage}>Show Message</Button>
<Button onClick={showNotification}>Show Notification</Button>
<Button danger onClick={showConfirm}>
Delete Item
</Button>
</Space>
);
}
export { FeedbackDemo };Why good: useApp() reads ConfigProvider context (theme, locale, prefixCls), all feedback renders consistently with the current theme.
// BAD: Using static methods
import { message, Modal } from "antd";
function BadFeedback() {
message.success("Saved!"); // Ignores ConfigProvider theme/locale
Modal.confirm({ title: "Sure?" }); // Ignores ConfigProvider context
}Why bad: Static methods create their own React root outside ConfigProvider, resulting in wrong theme colors, missing locale translations, and broken CSS variable references.
---
Declarative Modal
import { useState } from "react";
import { Modal, Button, Form, Input } from "antd";
function EditModal({ open, onClose }: { open: boolean; onClose: () => void }) {
const [form] = Form.useForm();
const handleOk = async () => {
const values = await form.validateFields();
await saveData(values);
onClose();
};
return (
<Modal
title="Edit Profile"
open={open}
onOk={handleOk}
onCancel={onClose}
destroyOnClose
>
<Form form={form} layout="vertical">
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
<Input />
</Form.Item>
</Form>
</Modal>
);
}
export { EditModal };When to use: Declarative Modal (open prop) for form modals and complex content, modal.confirm() via useApp for simple confirmation dialogs.
---
Settings Drawer
import { useState } from "react";
import { Drawer, Form, Input, Switch, Select, Button, Space, App } from "antd";
import { SettingOutlined } from "@ant-design/icons";
interface SettingsFormValues {
displayName: string;
emailNotifications: boolean;
language: string;
timezone: string;
}
const DRAWER_WIDTH = 480;
function SettingsDrawer() {
const [open, setOpen] = useState(false);
const [form] = Form.useForm<SettingsFormValues>();
const { message } = App.useApp();
const handleSave = async () => {
const values = await form.validateFields();
await updateSettings(values);
message.success("Settings saved");
setOpen(false);
};
return (
<>
<Button icon={<SettingOutlined />} onClick={() => setOpen(true)}>
Settings
</Button>
<Drawer
title="Settings"
width={DRAWER_WIDTH}
open={open}
onClose={() => setOpen(false)}
destroyOnClose
extra={
<Space>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button type="primary" onClick={handleSave}>
Save
</Button>
</Space>
}
>
<Form<SettingsFormValues>
form={form}
layout="vertical"
initialValues={{
emailNotifications: true,
language: "en",
timezone: "UTC",
}}
>
<Form.Item
name="displayName"
label="Display Name"
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<Form.Item
name="emailNotifications"
label="Email Notifications"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item name="language" label="Language">
<Select
options={[
{ label: "English", value: "en" },
{ label: "Chinese", value: "zh" },
{ label: "Japanese", value: "ja" },
]}
/>
</Form.Item>
<Form.Item name="timezone" label="Timezone">
<Select
showSearch
options={[
{ label: "UTC", value: "UTC" },
{ label: "US/Eastern", value: "US/Eastern" },
{ label: "US/Pacific", value: "US/Pacific" },
{ label: "Asia/Tokyo", value: "Asia/Tokyo" },
{ label: "Europe/London", value: "Europe/London" },
]}
/>
</Form.Item>
</Form>
</Drawer>
</>
);
}
export { SettingsDrawer };Ant Design -- Forms & Validation Examples
Form patterns with TypeScript generics, validation rules, dynamic fields, and modal forms. See SKILL.md for core concepts.
Related examples:
- Core Setup & Theming
- Feedback Components
- Tables & Data Display
---
Complex Form with Dependencies and Async Validation
import { Form, Input, Select, InputNumber, Divider, Button, App } from "antd";
import type { Rule } from "antd/es/form";
interface RegistrationFormValues {
username: string;
email: string;
password: string;
confirmPassword: string;
accountType: "personal" | "business";
companyName?: string;
employeeCount?: number;
}
const MIN_PASSWORD_LENGTH = 8;
const MAX_COMPANY_NAME_LENGTH = 100;
// Async validator to check username availability
const checkUsernameAvailable = async (_: Rule, value: string) => {
if (!value) return;
const response = await fetch(`/api/check-username?username=${value}`);
const { available } = await response.json();
if (!available) {
throw new Error("Username is already taken");
}
};
function RegistrationForm() {
const [form] = Form.useForm<RegistrationFormValues>();
const { message } = App.useApp();
const accountType = Form.useWatch("accountType", form);
const handleFinish = async (values: RegistrationFormValues) => {
try {
await registerUser(values);
message.success("Registration successful!");
} catch {
message.error("Registration failed. Please try again.");
}
};
return (
<Form<RegistrationFormValues>
form={form}
layout="vertical"
onFinish={handleFinish}
initialValues={{ accountType: "personal" }}
>
<Form.Item
name="username"
label="Username"
hasFeedback
rules={[
{ required: true, message: "Username is required" },
{ min: 3, max: 20, message: "Username must be 3-20 characters" },
{
pattern: /^[a-zA-Z0-9_]+$/,
message: "Only letters, numbers, and underscores",
},
{ validator: checkUsernameAvailable },
]}
validateDebounce={500}
>
<Input placeholder="Choose a username" />
</Form.Item>
<Form.Item
name="email"
label="Email"
rules={[
{ required: true, message: "Email is required" },
{ type: "email", message: "Enter a valid email" },
]}
>
<Input placeholder="you@example.com" />
</Form.Item>
<Form.Item
name="password"
label="Password"
rules={[
{ required: true, message: "Password is required" },
{
min: MIN_PASSWORD_LENGTH,
message: `Password must be at least ${MIN_PASSWORD_LENGTH} characters`,
},
]}
>
<Input.Password placeholder="Enter password" />
</Form.Item>
{/* Password confirmation with dependency on 'password' field */}
<Form.Item
name="confirmPassword"
label="Confirm Password"
dependencies={["password"]}
rules={[
{ required: true, message: "Please confirm your password" },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue("password") === value) {
return Promise.resolve();
}
return Promise.reject(new Error("Passwords do not match"));
},
}),
]}
>
<Input.Password placeholder="Confirm password" />
</Form.Item>
<Divider />
<Form.Item
name="accountType"
label="Account Type"
rules={[{ required: true }]}
>
<Select
options={[
{ label: "Personal", value: "personal" },
{ label: "Business", value: "business" },
]}
/>
</Form.Item>
{/* Conditional fields based on accountType */}
{accountType === "business" && (
<>
<Form.Item
name="companyName"
label="Company Name"
rules={[
{ required: true, message: "Company name is required" },
{ max: MAX_COMPANY_NAME_LENGTH },
]}
>
<Input placeholder="Your company name" />
</Form.Item>
<Form.Item
name="employeeCount"
label="Number of Employees"
rules={[{ required: true, message: "Employee count is required" }]}
>
<InputNumber min={1} style={{ width: "100%" }} />
</Form.Item>
</>
)}
<Form.Item>
<Button type="primary" htmlType="submit" block>
Register
</Button>
</Form.Item>
</Form>
);
}
export { RegistrationForm };---
Modal Form Pattern
import { useState } from "react";
import { Modal, Form, Input, Select, Button, App } from "antd";
import { PlusOutlined } from "@ant-design/icons";
interface InviteFormValues {
email: string;
role: "admin" | "member" | "viewer";
message?: string;
}
function InviteMemberModal() {
const [open, setOpen] = useState(false);
const [form] = Form.useForm<InviteFormValues>();
const [loading, setLoading] = useState(false);
const { message } = App.useApp();
const handleOk = async () => {
try {
const values = await form.validateFields();
setLoading(true);
await sendInvite(values);
message.success(`Invitation sent to ${values.email}`);
form.resetFields();
setOpen(false);
} catch {
// validateFields rejection is handled by Form UI
} finally {
setLoading(false);
}
};
const handleCancel = () => {
form.resetFields();
setOpen(false);
};
return (
<>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setOpen(true)}
>
Invite Member
</Button>
<Modal
title="Invite Team Member"
open={open}
onOk={handleOk}
onCancel={handleCancel}
confirmLoading={loading}
destroyOnClose
>
<Form<InviteFormValues>
form={form}
layout="vertical"
initialValues={{ role: "member" }}
>
<Form.Item
name="email"
label="Email Address"
rules={[
{ required: true, message: "Email is required" },
{ type: "email", message: "Enter a valid email" },
]}
>
<Input placeholder="colleague@company.com" />
</Form.Item>
<Form.Item name="role" label="Role" rules={[{ required: true }]}>
<Select
options={[
{ label: "Admin", value: "admin" },
{ label: "Member", value: "member" },
{ label: "Viewer", value: "viewer" },
]}
/>
</Form.Item>
<Form.Item name="message" label="Personal Message">
<Input.TextArea rows={3} placeholder="Optional welcome message" />
</Form.Item>
</Form>
</Modal>
</>
);
}
export { InviteMemberModal };---
Dynamic Fields with Form.List
import { Form, Input, Button, Space } from "antd";
import { PlusOutlined, MinusCircleOutlined } from "@ant-design/icons";
interface TeamFormValues {
teamName: string;
members: Array<{ name: string; email: string }>;
}
function TeamForm() {
const [form] = Form.useForm<TeamFormValues>();
return (
<Form<TeamFormValues> form={form} layout="vertical" onFinish={console.log}>
<Form.Item name="teamName" label="Team Name" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.List
name="members"
rules={[
{
validator: async (_, members: TeamFormValues["members"]) => {
if (!members || members.length < 1) {
return Promise.reject(new Error("At least 1 member required"));
}
},
},
]}
>
{(fields, { add, remove }, { errors }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<Space
key={key}
style={{ display: "flex", marginBottom: 8 }}
align="baseline"
>
<Form.Item
{...restField}
name={[name, "name"]}
rules={[{ required: true, message: "Member name required" }]}
>
<Input placeholder="Member name" />
</Form.Item>
<Form.Item
{...restField}
name={[name, "email"]}
rules={[
{
required: true,
type: "email",
message: "Valid email required",
},
]}
>
<Input placeholder="Email" />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => add()}
icon={<PlusOutlined />}
block
>
Add Member
</Button>
</Form.Item>
<Form.ErrorList errors={errors} />
</>
)}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
}
export { TeamForm };---
Form.useWatch for Reactive Fields
import { Form, Input, Select, InputNumber } from "antd";
function PricingForm() {
const [form] = Form.useForm();
const planType = Form.useWatch("planType", form);
return (
<Form form={form} layout="vertical">
<Form.Item name="planType" label="Plan">
<Select
options={[
{ label: "Free", value: "free" },
{ label: "Pro", value: "pro" },
{ label: "Enterprise", value: "enterprise" },
]}
/>
</Form.Item>
{/* Conditionally show fields based on watched value */}
{planType !== "free" && (
<Form.Item
name="seats"
label="Number of Seats"
rules={[{ required: true }]}
>
<InputNumber min={1} style={{ width: "100%" }} />
</Form.Item>
)}
</Form>
);
}
export { PricingForm };When to use: Form.useWatch is ideal for conditional rendering based on field values, avoids unnecessary re-renders compared to onValuesChange.
---
Typed Create Form with Validation
import { Form, Input, Select, Button, InputNumber, App } from "antd";
import type { Rule } from "antd/es/form";
interface CreateUserFormValues {
name: string;
email: string;
role: "admin" | "editor" | "viewer";
age: number;
}
const MIN_NAME_LENGTH = 2;
const MAX_NAME_LENGTH = 50;
const MIN_AGE = 18;
const MAX_AGE = 120;
const NAME_RULES: Rule[] = [
{ required: true, message: "Name is required" },
{
min: MIN_NAME_LENGTH,
max: MAX_NAME_LENGTH,
message: `Name must be ${MIN_NAME_LENGTH}-${MAX_NAME_LENGTH} characters`,
},
];
const EMAIL_RULES: Rule[] = [
{ required: true, message: "Email is required" },
{ type: "email", message: "Enter a valid email" },
];
function CreateUserForm({
onSubmit,
}: {
onSubmit: (values: CreateUserFormValues) => Promise<void>;
}) {
const [form] = Form.useForm<CreateUserFormValues>();
const { message } = App.useApp();
const handleFinish = async (values: CreateUserFormValues) => {
try {
await onSubmit(values);
message.success("User created successfully");
form.resetFields();
} catch {
message.error("Failed to create user");
}
};
return (
<Form<CreateUserFormValues>
form={form}
layout="vertical"
onFinish={handleFinish}
initialValues={{ role: "viewer" }}
>
<Form.Item name="name" label="Name" rules={NAME_RULES}>
<Input placeholder="Enter name" />
</Form.Item>
<Form.Item name="email" label="Email" rules={EMAIL_RULES}>
<Input placeholder="Enter email" />
</Form.Item>
<Form.Item name="role" label="Role" rules={[{ required: true }]}>
<Select
options={[
{ label: "Admin", value: "admin" },
{ label: "Editor", value: "editor" },
{ label: "Viewer", value: "viewer" },
]}
/>
</Form.Item>
<Form.Item
name="age"
label="Age"
rules={[
{ required: true, message: "Age is required" },
{
type: "number",
min: MIN_AGE,
max: MAX_AGE,
message: `Age must be ${MIN_AGE}-${MAX_AGE}`,
},
]}
>
<InputNumber style={{ width: "100%" }} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Create User
</Button>
</Form.Item>
</Form>
);
}
export { CreateUserForm };
export type { CreateUserFormValues };Ant Design -- Internationalization Examples
ConfigProvider locale, dayjs locale sync, and language switching patterns. See SKILL.md for core concepts.
Related examples:
- Core Setup & Theming
- Next.js Integration
---
Locale Switching with dayjs Sync
import { useState } from "react";
import { ConfigProvider, Select, DatePicker } from "antd";
import type { Locale } from "antd/es/locale";
import enUS from "antd/locale/en_US";
import zhCN from "antd/locale/zh_CN";
import jaJP from "antd/locale/ja_JP";
import dayjs from "dayjs";
import "dayjs/locale/zh-cn";
import "dayjs/locale/ja";
const LOCALE_MAP: Record<string, { antd: Locale; dayjs: string }> = {
en: { antd: enUS, dayjs: "en" },
zh: { antd: zhCN, dayjs: "zh-cn" },
ja: { antd: jaJP, dayjs: "ja" },
};
function I18nApp() {
const [lang, setLang] = useState("en");
const currentLocale = LOCALE_MAP[lang] ?? LOCALE_MAP.en;
dayjs.locale(currentLocale.dayjs);
return (
<ConfigProvider locale={currentLocale.antd}>
<Select
value={lang}
onChange={setLang}
style={{ width: 120 }}
options={[
{ label: "English", value: "en" },
{ label: "Chinese", value: "zh" },
{ label: "Japanese", value: "ja" },
]}
/>
<DatePicker />
</ConfigProvider>
);
}
export { I18nApp };Why good: ConfigProvider locale handles all antd component text. dayjs locale must be set separately for date/time formatting. Both are kept in sync.
Ant Design -- Layout Examples
Application layout, responsive grid, Flex, and Space patterns. See SKILL.md for core concepts.
Related examples:
- Navigation Components
- Data Display Components
- Core Setup & Theming
---
Application Layout (Sidebar + Header + Content)
import { Layout, Menu, Breadcrumb } from "antd";
import {
DashboardOutlined,
UserOutlined,
SettingOutlined,
} from "@ant-design/icons";
import type { MenuProps } from "antd";
const { Header, Content, Sider, Footer } = Layout;
const SIDER_WIDTH = 200;
const MENU_ITEMS: MenuProps["items"] = [
{ key: "dashboard", icon: <DashboardOutlined />, label: "Dashboard" },
{ key: "users", icon: <UserOutlined />, label: "Users" },
{ key: "settings", icon: <SettingOutlined />, label: "Settings" },
];
function AppLayout({ children }: { children: React.ReactNode }) {
return (
<Layout style={{ minHeight: "100vh" }}>
<Sider width={SIDER_WIDTH} collapsible>
<Menu
theme="dark"
mode="inline"
defaultSelectedKeys={["dashboard"]}
items={MENU_ITEMS}
/>
</Sider>
<Layout>
<Header style={{ padding: 0 }} />
<Content style={{ margin: "16px" }}>
<Breadcrumb items={[{ title: "Home" }, { title: "Dashboard" }]} />
<div style={{ padding: 24, minHeight: 360 }}>{children}</div>
</Content>
<Footer style={{ textAlign: "center" }}>My App</Footer>
</Layout>
</Layout>
);
}
export { AppLayout };---
Grid System (24-Column)
import { Row, Col } from "antd";
const GUTTER_RESPONSIVE = { xs: 8, sm: 16, md: 24, lg: 32 } as const;
function ResponsiveGrid() {
return (
<Row gutter={[GUTTER_RESPONSIVE, 16]}>
{/* Full width on mobile, 1/3 on medium+ */}
<Col xs={24} md={8}>
<Card title="Panel 1" />
</Col>
<Col xs={24} md={8}>
<Card title="Panel 2" />
</Col>
<Col xs={24} md={8}>
<Card title="Panel 3" />
</Col>
</Row>
);
}
export { ResponsiveGrid };---
Flex Component (v5.10+)
import { Flex, Button } from "antd";
const FLEX_GAP = 8;
function FlexExample() {
return (
<Flex gap={FLEX_GAP} justify="space-between" align="center" wrap>
<Button type="primary">Save</Button>
<Button>Cancel</Button>
<Button type="link">Reset</Button>
</Flex>
);
}
export { FlexExample };When to use: Use Layout for page-level structure, Grid (Row/Col) for responsive content areas, Flex for inline element alignment, Space for uniform gaps between small elements.
Ant Design -- Navigation & Icons Examples
Menu, Breadcrumb, and icon tree-shaking patterns. See SKILL.md for core concepts.
Related examples:
- Layout
- Pro Components
---
Menu with Items API
import { Menu } from "antd";
import type { MenuProps } from "antd";
import {
HomeOutlined,
AppstoreOutlined,
SettingOutlined,
MailOutlined,
} from "@ant-design/icons";
type MenuItem = Required<MenuProps>["items"][number];
const MENU_ITEMS: MenuItem[] = [
{ key: "home", icon: <HomeOutlined />, label: "Home" },
{
key: "products",
icon: <AppstoreOutlined />,
label: "Products",
children: [
{ key: "product-list", label: "Product List" },
{ key: "add-product", label: "Add Product" },
],
},
{
key: "settings",
icon: <SettingOutlined />,
label: "Settings",
children: [
{ key: "profile", label: "Profile" },
{ key: "security", label: "Security" },
],
},
{ key: "contact", icon: <MailOutlined />, label: "Contact" },
];
function NavigationMenu({ onSelect }: { onSelect: (key: string) => void }) {
return (
<Menu
mode="inline"
defaultSelectedKeys={["home"]}
defaultOpenKeys={["products"]}
items={MENU_ITEMS}
onClick={({ key }) => onSelect(key)}
/>
);
}
export { NavigationMenu };Why good: Uses items API (v4.20+) instead of JSX children pattern, proper TypeScript MenuItem type, named constants.
---
Breadcrumb
import { Breadcrumb } from "antd";
import { HomeOutlined } from "@ant-design/icons";
function PageBreadcrumb({ current }: { current: string }) {
return (
<Breadcrumb
items={[
{ href: "/", title: <HomeOutlined /> },
{ href: "/users", title: "Users" },
{ title: current },
]}
/>
);
}
export { PageBreadcrumb };---
Icon Imports (Tree-Shaking)
// GOOD: Import individual icons for tree-shaking
import { UserOutlined, SearchOutlined, PlusOutlined } from "@ant-design/icons";
// GOOD: Alternative explicit path import (best tree-shaking)
import UserOutlined from "@ant-design/icons/UserOutlined";
// BAD: Never import the entire icon set
import * as Icons from "@ant-design/icons"; // Adds 500KB+ to bundle---
Custom Icons from SVG
import Icon from "@ant-design/icons";
import type { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon";
const CustomSvg = () => (
<svg viewBox="0 0 1024 1024" fill="currentColor" width="1em" height="1em">
<path d="M512 0C229.2 0 0 229.2 0 512s229.2 512 512 512 512-229.2 512-512S794.8 0 512 0z" />
</svg>
);
const CustomIcon = (props: Partial<CustomIconComponentProps>) => (
<Icon component={CustomSvg} {...props} />
);
export { CustomIcon };Ant Design -- Next.js Integration Examples
AntdRegistry, SSR setup, client components, and App Router patterns. See SKILL.md for core concepts.
Related examples:
- Core Setup & Theming
- Feedback Components
---
Complete Layout with Theme and Locale
// app/layout.tsx
import { AntdRegistry } from "@ant-design/nextjs-registry";
import { ConfigProvider, App as AntApp } from "antd";
import type { ThemeConfig } from "antd";
import enUS from "antd/locale/en_US";
const THEME: ThemeConfig = {
cssVar: true,
hashed: false,
token: {
colorPrimary: "#2563eb",
borderRadius: 8,
fontSize: 14,
},
components: {
Button: { algorithm: true },
},
};
function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AntdRegistry>
<ConfigProvider theme={THEME} locale={enUS}>
<AntApp>{children}</AntApp>
</ConfigProvider>
</AntdRegistry>
</body>
</html>
);
}
export { RootLayout };Why good: AntdRegistry extracts first-screen styles into HTML to prevent FOUC (flash of unstyled content). Wrapping order matters: AntdRegistry > ConfigProvider > App.
---
Client Component for Interactive Features
// components/interactive-section.tsx
"use client";
import { Button, Space, App } from "antd";
import { PlusOutlined } from "@ant-design/icons";
function InteractiveSection() {
const { message } = App.useApp();
const handleCreate = () => {
message.success("Item created!");
};
return (
<Space>
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
Create
</Button>
</Space>
);
}
export { InteractiveSection };---
Sub-Component Workaround (App Router)
// Next.js App Router does NOT support dot notation for sub-components
// BAD in App Router:
// <Select.Option value="a">A</Select.Option>
// <Typography.Text>Hello</Typography.Text>
// GOOD: Import sub-components directly
import { Select, Typography } from "antd";
const { Option } = Select;
const { Text, Title, Paragraph } = Typography;
// Or use the items/options API instead of JSX children
<Select
options={[
{ label: "Option A", value: "a" },
{ label: "Option B", value: "b" },
]}
/>;Why this matters: Next.js App Router server components cannot resolve dot-notation sub-components. Use destructuring or the data-driven API.
Ant Design -- Pro Components Examples
ProLayout, ProTable, ProForm, and StepsForm patterns. See SKILL.md for core concepts.
Related examples:
- Tables & Data Display
- Forms & Validation
- Layout
---
ProLayout with Route-Based Menu
import { ProLayout } from "@ant-design/pro-components";
import type { ProLayoutProps } from "@ant-design/pro-components";
import {
DashboardOutlined,
UserOutlined,
ShoppingCartOutlined,
SettingOutlined,
} from "@ant-design/icons";
const ROUTE_CONFIG: ProLayoutProps["route"] = {
path: "/",
routes: [
{ path: "/dashboard", name: "Dashboard", icon: <DashboardOutlined /> },
{
path: "/users",
name: "Users",
icon: <UserOutlined />,
routes: [
{ path: "/users/list", name: "User List" },
{ path: "/users/roles", name: "Roles & Permissions" },
],
},
{
path: "/orders",
name: "Orders",
icon: <ShoppingCartOutlined />,
routes: [
{ path: "/orders/list", name: "Order List" },
{ path: "/orders/returns", name: "Returns" },
],
},
{ path: "/settings", name: "Settings", icon: <SettingOutlined /> },
],
};
function AdminLayout({ children }: { children: React.ReactNode }) {
return (
<ProLayout
title="Admin Portal"
logo="/logo.svg"
route={ROUTE_CONFIG}
layout="mix"
fixedHeader
fixSiderbar
menuItemRender={(item, dom) => <a href={item.path}>{dom}</a>}
>
{children}
</ProLayout>
);
}
export { AdminLayout };---
ProTable with Full CRUD
import { useRef } from "react";
import {
ProTable,
ModalForm,
ProFormText,
ProFormSelect,
} from "@ant-design/pro-components";
import type { ProColumns, ActionType } from "@ant-design/pro-components";
import { Button, App, Popconfirm } from "antd";
import { PlusOutlined } from "@ant-design/icons";
interface ProductRecord {
id: string;
name: string;
category: string;
price: number;
stock: number;
status: "active" | "draft" | "archived";
}
function ProductManagement() {
const actionRef = useRef<ActionType>();
const { message } = App.useApp();
const columns: ProColumns<ProductRecord>[] = [
{
title: "Product Name",
dataIndex: "name",
copyable: true,
ellipsis: true,
formItemProps: { rules: [{ required: true }] },
},
{
title: "Category",
dataIndex: "category",
valueEnum: {
electronics: { text: "Electronics" },
clothing: { text: "Clothing" },
books: { text: "Books" },
home: { text: "Home & Garden" },
},
},
{
title: "Price",
dataIndex: "price",
valueType: "money",
sorter: true,
hideInSearch: true,
},
{
title: "Stock",
dataIndex: "stock",
valueType: "digit",
sorter: true,
hideInSearch: true,
},
{
title: "Status",
dataIndex: "status",
valueEnum: {
active: { text: "Active", status: "Success" },
draft: { text: "Draft", status: "Default" },
archived: { text: "Archived", status: "Error" },
},
},
{
title: "Actions",
valueType: "option",
width: 180,
render: (_, record) => [
<a key="edit" onClick={() => handleEdit(record)}>
Edit
</a>,
<Popconfirm
key="delete"
title="Delete this product?"
onConfirm={async () => {
await deleteProduct(record.id);
message.success("Product deleted");
actionRef.current?.reload();
}}
>
<a style={{ color: "red" }}>Delete</a>
</Popconfirm>,
],
},
];
return (
<ProTable<ProductRecord>
columns={columns}
actionRef={actionRef}
request={async (params, sort, filter) => {
const response = await fetchProducts({ ...params, sort, filter });
return {
data: response.items,
success: true,
total: response.total,
};
}}
rowKey="id"
search={{ labelWidth: "auto" }}
pagination={{ defaultPageSize: 20 }}
dateFormatter="string"
headerTitle="Product Management"
toolBarRender={() => [
<ModalForm<Omit<ProductRecord, "id">>
key="add"
title="Add Product"
trigger={
<Button type="primary" icon={<PlusOutlined />}>
Add Product
</Button>
}
onFinish={async (values) => {
await createProduct(values);
message.success("Product created");
actionRef.current?.reload();
return true; // Close modal
}}
>
<ProFormText
name="name"
label="Product Name"
rules={[{ required: true }]}
/>
<ProFormSelect
name="category"
label="Category"
options={[
{ label: "Electronics", value: "electronics" },
{ label: "Clothing", value: "clothing" },
{ label: "Books", value: "books" },
{ label: "Home & Garden", value: "home" },
]}
rules={[{ required: true }]}
/>
<ProFormText
name="price"
label="Price"
rules={[{ required: true }]}
/>
<ProFormText
name="stock"
label="Stock"
rules={[{ required: true }]}
/>
</ModalForm>,
]}
/>
);
}
export { ProductManagement };---
ProForm StepsForm (Wizard)
import {
ProForm,
ProFormText,
ProFormSelect,
StepsForm,
} from "@ant-design/pro-components";
function CreateProjectWizard() {
return (
<StepsForm
onFinish={async (values) => {
await createProject(values);
return true;
}}
>
<StepsForm.StepForm name="basic" title="Basic Info">
<ProFormText
name="name"
label="Project Name"
rules={[{ required: true }]}
/>
<ProFormText name="description" label="Description" />
</StepsForm.StepForm>
<StepsForm.StepForm name="config" title="Configuration">
<ProFormSelect
name="type"
label="Project Type"
options={[
{ label: "Web App", value: "web" },
{ label: "API", value: "api" },
{ label: "Library", value: "lib" },
]}
rules={[{ required: true }]}
/>
</StepsForm.StepForm>
<StepsForm.StepForm name="review" title="Review">
<ProForm.Group>{/* Summary fields */}</ProForm.Group>
</StepsForm.StepForm>
</StepsForm>
);
}
export { CreateProjectWizard };When to use: ProTable for CRUD pages with search/filter, ProForm for step-by-step wizards and modal/drawer forms, ProLayout for admin shell with route-based menu generation.
Ant Design -- Table Examples
Table patterns with sorting, filtering, pagination, row selection, virtual scrolling, and expandable rows. See SKILL.md for core concepts.
Related examples:
- Data Display Components
- Forms & Validation
- Pro Components
---
Server-Side Table with Sorting, Filtering, and Selection
import { useState, useCallback } from "react";
import { Table, Button, Space, Tag, Input, App } from "antd";
import type { ColumnsType, TablePaginationConfig } from "antd/es/table";
import type { FilterValue, SorterResult } from "antd/es/table/interface";
import {
SearchOutlined,
ExportOutlined,
DeleteOutlined,
} from "@ant-design/icons";
interface OrderRecord {
id: string;
customerName: string;
email: string;
amount: number;
status: "pending" | "processing" | "shipped" | "delivered" | "cancelled";
createdAt: string;
}
interface TableParams {
pagination: TablePaginationConfig;
sortField?: string;
sortOrder?: "ascend" | "descend";
filters: Record<string, FilterValue | null>;
}
const DEFAULT_PAGE_SIZE = 20;
const STATUS_CONFIG: Record<
OrderRecord["status"],
{ color: string; label: string }
> = {
pending: { color: "default", label: "Pending" },
processing: { color: "processing", label: "Processing" },
shipped: { color: "blue", label: "Shipped" },
delivered: { color: "success", label: "Delivered" },
cancelled: { color: "error", label: "Cancelled" },
};
function OrderTable() {
const { message, modal } = App.useApp();
const [data, setData] = useState<OrderRecord[]>([]);
const [loading, setLoading] = useState(false);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [tableParams, setTableParams] = useState<TableParams>({
pagination: { current: 1, pageSize: DEFAULT_PAGE_SIZE },
filters: {},
});
const fetchData = useCallback(async (params: TableParams) => {
setLoading(true);
try {
const response = await fetch("/api/orders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
page: params.pagination.current,
pageSize: params.pagination.pageSize,
sortField: params.sortField,
sortOrder: params.sortOrder,
filters: params.filters,
}),
});
const result = await response.json();
setData(result.items);
setTableParams((prev) => ({
...prev,
pagination: { ...prev.pagination, total: result.total },
}));
} finally {
setLoading(false);
}
}, []);
const handleTableChange = (
pagination: TablePaginationConfig,
filters: Record<string, FilterValue | null>,
sorter: SorterResult<OrderRecord> | SorterResult<OrderRecord>[],
) => {
const singleSorter = Array.isArray(sorter) ? sorter[0] : sorter;
const newParams: TableParams = {
pagination,
filters,
sortField: singleSorter?.field as string | undefined,
sortOrder: singleSorter?.order ?? undefined,
};
setTableParams(newParams);
fetchData(newParams);
};
const handleBulkDelete = () => {
modal.confirm({
title: `Delete ${selectedRowKeys.length} orders?`,
content: "This action cannot be undone.",
okText: "Delete",
okType: "danger",
onOk: async () => {
await deleteOrders(selectedRowKeys as string[]);
message.success(`Deleted ${selectedRowKeys.length} orders`);
setSelectedRowKeys([]);
fetchData(tableParams);
},
});
};
const columns: ColumnsType<OrderRecord> = [
{
title: "Customer",
dataIndex: "customerName",
sorter: true,
ellipsis: true,
filterDropdown: ({
setSelectedKeys,
selectedKeys,
confirm,
clearFilters,
}) => (
<div style={{ padding: 8 }}>
<Input
placeholder="Search customer"
value={selectedKeys[0]}
onChange={(e) =>
setSelectedKeys(e.target.value ? [e.target.value] : [])
}
onPressEnter={() => confirm()}
style={{ marginBottom: 8, display: "block" }}
/>
<Space>
<Button
type="primary"
onClick={() => confirm()}
size="small"
icon={<SearchOutlined />}
>
Search
</Button>
<Button onClick={() => clearFilters?.()} size="small">
Reset
</Button>
</Space>
</div>
),
filterIcon: (filtered) => (
<SearchOutlined style={{ color: filtered ? "#1677ff" : undefined }} />
),
},
{
title: "Amount",
dataIndex: "amount",
sorter: true,
align: "right",
render: (amount: number) => `$${amount.toFixed(2)}`,
},
{
title: "Status",
dataIndex: "status",
filters: Object.entries(STATUS_CONFIG).map(([value, config]) => ({
text: config.label,
value,
})),
render: (status: OrderRecord["status"]) => {
const config = STATUS_CONFIG[status];
return <Tag color={config.color}>{config.label}</Tag>;
},
},
{
title: "Created",
dataIndex: "createdAt",
sorter: true,
render: (date: string) => new Date(date).toLocaleDateString(),
},
];
return (
<>
<Space style={{ marginBottom: 16 }}>
<Button icon={<ExportOutlined />}>Export</Button>
{selectedRowKeys.length > 0 && (
<Button danger icon={<DeleteOutlined />} onClick={handleBulkDelete}>
Delete ({selectedRowKeys.length})
</Button>
)}
</Space>
<Table<OrderRecord>
columns={columns}
dataSource={data}
rowKey="id"
loading={loading}
pagination={{
...tableParams.pagination,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) =>
`${range[0]}-${range[1]} of ${total} items`,
}}
rowSelection={{
selectedRowKeys,
onChange: setSelectedRowKeys,
preserveSelectedRowKeys: true,
}}
onChange={handleTableChange}
/>
</>
);
}
export { OrderTable };
export type { OrderRecord };---
Expandable Table with Summary Row
import { Table } from "antd";
import type { ColumnsType } from "antd/es/table";
interface InvoiceItem {
id: string;
description: string;
quantity: number;
unitPrice: number;
notes?: string;
}
const columns: ColumnsType<InvoiceItem> = [
{ title: "Description", dataIndex: "description" },
{ title: "Qty", dataIndex: "quantity", align: "right" },
{
title: "Unit Price",
dataIndex: "unitPrice",
align: "right",
render: (price: number) => `$${price.toFixed(2)}`,
},
{
title: "Total",
key: "total",
align: "right",
render: (_, record) =>
`$${(record.quantity * record.unitPrice).toFixed(2)}`,
},
];
function InvoiceTable({ items }: { items: InvoiceItem[] }) {
return (
<Table<InvoiceItem>
columns={columns}
dataSource={items}
rowKey="id"
pagination={false}
expandable={{
expandedRowRender: (record) =>
record.notes ? <p style={{ margin: 0 }}>{record.notes}</p> : null,
rowExpandable: (record) => !!record.notes,
}}
summary={(pageData) => {
const total = pageData.reduce(
(sum, item) => sum + item.quantity * item.unitPrice,
0,
);
return (
<Table.Summary.Row>
<Table.Summary.Cell index={0} colSpan={3}>
<strong>Grand Total</strong>
</Table.Summary.Cell>
<Table.Summary.Cell index={1} align="right">
<strong>${total.toFixed(2)}</strong>
</Table.Summary.Cell>
</Table.Summary.Row>
);
}}
/>
);
}
export { InvoiceTable };---
Client-Side Table with Sorting and Filtering
import { Table, Tag, Space, Button } from "antd";
import type { ColumnsType, TableProps } from "antd/es/table";
interface UserRecord {
id: string;
name: string;
email: string;
role: "admin" | "editor" | "viewer";
status: "active" | "inactive";
lastLogin: string;
}
const ROLE_COLORS: Record<UserRecord["role"], string> = {
admin: "red",
editor: "blue",
viewer: "green",
};
const STATUS_FILTERS = [
{ text: "Active", value: "active" },
{ text: "Inactive", value: "inactive" },
] as const;
const PAGE_SIZE = 20;
const columns: ColumnsType<UserRecord> = [
{
title: "Name",
dataIndex: "name",
key: "name",
sorter: (a, b) => a.name.localeCompare(b.name),
ellipsis: true,
},
{
title: "Email",
dataIndex: "email",
key: "email",
},
{
title: "Role",
dataIndex: "role",
key: "role",
render: (role: UserRecord["role"]) => (
<Tag color={ROLE_COLORS[role]}>{role.toUpperCase()}</Tag>
),
filters: [
{ text: "Admin", value: "admin" },
{ text: "Editor", value: "editor" },
{ text: "Viewer", value: "viewer" },
],
onFilter: (value, record) => record.role === value,
},
{
title: "Status",
dataIndex: "status",
key: "status",
filters: [...STATUS_FILTERS],
onFilter: (value, record) => record.status === value,
render: (status: UserRecord["status"]) => (
<Tag color={status === "active" ? "green" : "default"}>{status}</Tag>
),
},
{
title: "Actions",
key: "actions",
render: (_, record) => (
<Space>
<Button type="link" onClick={() => handleEdit(record)}>
Edit
</Button>
<Button type="link" danger onClick={() => handleDelete(record.id)}>
Delete
</Button>
</Space>
),
},
];
function UserTable({
data,
loading,
}: {
data: UserRecord[];
loading: boolean;
}) {
const handleChange: TableProps<UserRecord>["onChange"] = (
pagination,
filters,
sorter,
) => {
// Handle table change events
};
return (
<Table<UserRecord>
columns={columns}
dataSource={data}
rowKey="id"
loading={loading}
pagination={{
pageSize: PAGE_SIZE,
showSizeChanger: true,
showTotal: (total) => `Total ${total} items`,
}}
onChange={handleChange}
/>
);
}
export { UserTable };
export type { UserRecord };---
Virtual Scrolling (Large Datasets)
import { Table } from "antd";
import type { ColumnsType } from "antd/es/table";
const VIRTUAL_SCROLL_HEIGHT = 500;
const VIRTUAL_SCROLL_WIDTH = 1200;
const columns: ColumnsType<DataRecord> = [
{ title: "ID", dataIndex: "id", width: 100 },
{ title: "Name", dataIndex: "name", width: 200 },
{ title: "Value", dataIndex: "value", width: 150 },
];
function VirtualTable({ data }: { data: DataRecord[] }) {
return (
<Table<DataRecord>
columns={columns}
dataSource={data}
rowKey="id"
virtual
scroll={{ x: VIRTUAL_SCROLL_WIDTH, y: VIRTUAL_SCROLL_HEIGHT }}
pagination={false}
/>
);
}
export { VirtualTable };Important: Virtual scrolling requires both scroll.x and scroll.y to be set as numbers. All columns should have explicit width values to avoid alignment issues.
# yaml-language-server: $schema=https://raw.githubusercontent.com/agents-inc/cli/main/src/schemas/metadata.schema.json
category: web-ui-components
slug: ant-design
domain: web
author: "@vince"
displayName: Ant Design
cliDescription: Ant Design enterprise UI library for React
usageGuidance: Use when building React UIs with Ant Design — ConfigProvider theming, Table, Form, Layout, data entry components, and enterprise design patterns.
Ant Design Quick Reference
Decision frameworks, component checklists, and ConfigProvider options for Ant Design. See SKILL.md for core concepts and examples/ for full code examples.
---
ConfigProvider Props Reference
| Prop | Type | Description |
|---|---|---|
theme | ThemeConfig | Design token configuration |
locale | Locale | Component text locale (import from antd/locale/*) |
direction | `"ltr" \ | "rtl"` |
componentSize | `"small" \ | "middle" \ |
prefixCls | string | CSS class prefix (default: ant) |
getPopupContainer | (triggerNode) => HTMLElement | Popup mount target |
autoInsertSpaceInButton | boolean | Auto-insert space between 2 CJK characters in Button |
componentDisabled | boolean | Disable all components globally |
---
ThemeConfig Shape
import type { ThemeConfig } from "antd";
const theme: ThemeConfig = {
// CSS variables mode for efficient theme switching
cssVar: true, // or { key: "my-app" } for React <18
// Disable hashed class names (safe with single antd version)
hashed: false,
// Theme algorithm(s)
algorithm: theme.defaultAlgorithm, // or darkAlgorithm, compactAlgorithm, or array
// Global design tokens (Seed tokens)
token: {
colorPrimary: "#1677ff",
colorSuccess: "#52c41a",
colorWarning: "#faad14",
colorError: "#ff4d4f",
colorInfo: "#1677ff",
borderRadius: 6,
fontSize: 14,
fontFamily: "...",
wireframe: false, // true for wireframe style
},
// Component-level token overrides
components: {
Button: {
colorPrimary: "#00b96b",
algorithm: true, // derive other tokens from this colorPrimary
},
Table: {
headerBg: "#fafafa",
rowHoverBg: "#f0f7ff",
},
},
};---
Seed Tokens (Foundational)
| Token | Default | Description |
|---|---|---|
colorPrimary | #1677ff | Brand primary color |
colorSuccess | #52c41a | Success state color |
colorWarning | #faad14 | Warning state color |
colorError | #ff4d4f | Error/danger state color |
colorInfo | #1677ff | Informational color |
borderRadius | 6 | Base border radius (px) |
fontSize | 14 | Base font size (px) |
fontFamily | System fonts | Font stack |
wireframe | false | Wireframe visual style |
colorBgBase | #fff | Base background color |
colorTextBase | #000 | Base text color |
sizeUnit | 4 | Base sizing unit |
sizeStep | 4 | Sizing step increment |
controlHeight | 32 | Default control height (px) |
lineWidth | 1 | Default border width |
lineType | solid | Default border style |
motionUnit | 0.1 | Animation base unit (seconds) |
---
Theme Algorithms
| Algorithm | Import | Use Case |
|---|---|---|
defaultAlgorithm | theme.defaultAlgorithm | Light mode (default) |
darkAlgorithm | theme.darkAlgorithm | Dark mode |
compactAlgorithm | theme.compactAlgorithm | Dense/compact spacing |
Algorithms can be combined: algorithm: [theme.darkAlgorithm, theme.compactAlgorithm]
---
Table Component Checklist
- [ ] Generic type applied:
<Table<RecordType>>andColumnsType<RecordType> - [ ]
rowKeyset (string key or function) - [ ] Pagination configured (or
pagination={false}if virtual) - [ ]
loadingprop connected to data fetch state - [ ] Column
keyset for each column - [ ] Virtual mode has explicit column
widthvalues and bothscroll.xandscroll.yas numbers - [ ]
onChangehandler typed:TableProps<RecordType>["onChange"] - [ ] Filters use
onFilterfor client-side or server-side pagination params
---
Form Component Checklist
- [ ]
Form.useForm<T>()with TypeScript generic for field types - [ ]
initialValuesset on<Form>(not individualForm.Item) - [ ]
onFinishhandler for successful validation - [ ] Rules defined on
Form.Itemwith proper messages - [ ]
layoutset:"vertical"|"horizontal"|"inline" - [ ]
destroyOnCloseon Modal/Drawer containing the Form - [ ]
htmlType="submit"on the submit button - [ ]
Form.useWatchfor conditional field rendering (notonValuesChange) - [ ]
Form.Listfor dynamic field arrays withadd/removeoperations
---
Common Imports
// Core components
import { ConfigProvider, App, Button, Space, Flex, Divider } from "antd";
// Layout
import { Layout, Row, Col, Grid } from "antd";
const { Header, Content, Sider, Footer } = Layout;
// Data display
import {
Table,
Card,
Descriptions,
Statistic,
List,
Tree,
Tag,
Badge,
Tooltip,
} from "antd";
// Data entry
import {
Form,
Input,
Select,
DatePicker,
InputNumber,
Checkbox,
Radio,
Switch,
Upload,
Transfer,
Cascader,
AutoComplete,
} from "antd";
// Navigation
import { Menu, Breadcrumb, Pagination, Steps, Tabs, Dropdown } from "antd";
// Feedback
import { Modal, Drawer, Spin, Alert, Result, Progress } from "antd";
// NOTE: message, notification - use App.useApp() instead of static imports
// Theme
import { theme } from "antd";
const { useToken, defaultAlgorithm, darkAlgorithm, compactAlgorithm } = theme;
// Types
import type { ThemeConfig, MenuProps, TableProps, FormInstance } from "antd";
import type { ColumnsType } from "antd/es/table";
import type { Rule } from "antd/es/form";
import type { Locale } from "antd/es/locale";
// Icons (import individually)
import { UserOutlined, SearchOutlined, PlusOutlined } from "@ant-design/icons";
// Locale
import enUS from "antd/locale/en_US";
// Pro Components
import {
ProTable,
ProForm,
ProLayout,
ProDescriptions,
StepsForm,
ModalForm,
DrawerForm,
QueryFilter,
} from "@ant-design/pro-components";
import type { ProColumns, ActionType } from "@ant-design/pro-components";
// Next.js
import { AntdRegistry } from "@ant-design/nextjs-registry";---
Component Quick Decision Matrix
| Need | Component | Key Props |
|---|---|---|
| Page shell | Layout + Sider + Header + Content | collapsible, width |
| Responsive grid | Row + Col | gutter, xs/sm/md/lg/xl |
| Inline alignment | Flex | gap, justify, align, wrap |
| Uniform spacing | Space | size, direction, wrap |
| Data table | Table | columns, dataSource, rowKey, virtual |
| CRUD table | ProTable | request, columns, search, toolBarRender |
| Detail view | Descriptions | items, bordered, column |
| Form | Form | form, layout, onFinish, initialValues |
| Step wizard | StepsForm | onFinish, StepForm children |
| Modal form | ModalForm | trigger, onFinish, title |
| Sidebar menu | Menu | items, mode="inline", onClick |
| Tabs | Tabs | items, onChange, activeKey |
| Confirm action | modal.confirm() | via App.useApp() |
| Toast message | message.success() | via App.useApp() |
| Notification | notification.open() | via App.useApp() |
| Side panel | Drawer | open, onClose, placement, width |
| Loading state | Spin | spinning, size, tip |
| Status display | Tag or Badge | color, status |
| Stat card | Statistic | title, value, prefix, suffix |
---
Bundle Size Tips
1. Icons: Import individually, never import * as Icons 2. CSS Variables: Enable cssVar: true to reduce runtime style generation 3. Hashed: Set hashed: false when only one antd version exists 4. Dynamic imports: Lazy-load heavy page components 5. dayjs: Default date library (2KB), no action needed 6. Pro Components: Import only what you use from @ant-design/pro-components
---
Locale Files
Import from antd/locale/{locale_code}:
| Language | Import |
|---|---|
| English (US) | antd/locale/en_US |
| English (UK) | antd/locale/en_GB |
| Chinese (Simplified) | antd/locale/zh_CN |
| Chinese (Traditional) | antd/locale/zh_TW |
| Japanese | antd/locale/ja_JP |
| Korean | antd/locale/ko_KR |
| French | antd/locale/fr_FR |
| German | antd/locale/de_DE |
| Spanish | antd/locale/es_ES |
| Portuguese (Brazil) | antd/locale/pt_BR |
| Russian | antd/locale/ru_RU |
| Arabic | antd/locale/ar_EG |
Note: ConfigProvider locale only covers antd component text. Set dayjs locale separately for date/time formatting.
---
v5 to v6 Migration Notes
Ant Design v6 was released November 2025 and is the current major version (6.3.x as of March 2026). v5 is in a 1-year maintenance period.
Key v6 changes:
- CSS Variables mode is now the default (was opt-in with
cssVar: truein v5) - Zero-runtime mode available via
zeroRuntime: truein theme config (use with@ant-design/static-style-extract) - React 18+ required (React 17 dropped); React 19 fully supported without patches
- IE support completely removed
- DOM structure changes for better semantics -- some component tokens from the v4-to-v5 migration were cleaned up
findDOMNodecompatibility logic removed- New components: Masonry, resizable Drawer, InputNumber spinner mode, Tooltip panning
- Logical positioning APIs use
start/endinstead of directional terms (better RTL support) @ant-design/icons@6required (not compatible with antd@5 -- upgrade both together)- React Compiler enabled in bundled outputs for performance improvements
Upgrade path: v6 is designed as a smooth upgrade from v5. Most component APIs remain compatible. Remove @ant-design/v5-patch-for-react-19 if previously used. Check console for deprecation warnings on v5 before upgrading.
All patterns in this skill apply to both v5 and v6 unless noted.
Related skills
FAQ
Which Ant Design version does the skill target?
v6.x is current (pure CSS variables by default, React 18+); v5.x is in maintenance, and patterns apply to both unless noted.
How should theming be done?
Wrap the app with ConfigProvider and use the three-layer token system (Seed, Map, Alias); never override component styles with global CSS.