React: UI Component Library
Last updated: 2026-08-26
Tom started from scratch styling the admin panel for his blog. Six months later, the code was riddled with repetitive
style={{}}inline styles and hand-coded CSS classe names. Development of new features was slowing down, and the UI styles were inconsistent across different pages. He knew it was time to adopt a professional UI component library, but faced with numerous options—such as Ant Design, Material UI, and Headless UI—Tom was at a loss.
1. What You'll Learn
- Design Philosophies and Selection Criteria for Three Major UI Component Libraries (Ant Design / Material UI / Headless UI)
- Ant Design Configuration, Theme Customization, and Internationalization
- A Customization Solution for Unstyled Components Using Headless UI and Tailwind CSS
- On-Demand Loading of Component Libraries and Tree Shaking for Performance Optimization
- The Best Way to Integrate a Component Library with Next.js (dynamic import, Client Component boundaries)
- Theme Customization and Internationalization Configuration Solutions (ConfigProvider, ThemeProvider)
- Component library performance optimization: dynamic imports, tree shaking, optimizePackageImports
- Strategies and Boundary Management for Mixing Multiple Component Libraries
2. Conceptual Diagrams
Tom outlined the decision-making process for selecting a UI component library: based on three factors—project type, team background, and customization needs—he selected the most suitable component library solution. Different types of component libraries have their own advantages and limitations in various scenarios.
The decision tree first branches by project type: back-end and middle-tier projects prioritize component completeness, while front-end projects prioritize visual effects and design guidelines. Team background directly impacts the learning curve and development efficiency: React teams get up to speed faster with Ant Design, while design-driven teams find it smoother to use Material UI in conjunction with Figma design guidelines. The final decision point is on-demand loading and performance optimization—regardless of which library is chosen, proper on-demand loading configuration must be set up when integrating it into Next.js.
flowchart TD
A[Select UI Component Library] --> B{Project Type?}
B -->|Backend Management/Enterprise Applications| C[Ant Design<br/>Comprehensive enterprise-level components]
B -->|Consumer-Facing Front End| D[Material UI<br/>Modern Visual Style]
B -->|Highly Customized Requirements| E[Headless UI<br/>No style + Customizable]
C --> F{Team Background?}
D --> F
E --> F
F -->|React Team| G[Ant Design<br/>React Best Ecology]
F -->|Designer-led| H[Material UI<br/>Mature design specifications]
F -->|Fully Customizable Design| I[Headless UI<br/>Full Control Over Styles]
G --> J[Integrated into Next.js]
H --> J
I --> J
J --> K[Load on Demand Tree Shaking]
K --> L[Theme Customization]
L --> M[Performance Optimization]
3. A Real-Life Scenario
Tom’s admin dashboard was initially styled entirely by hand. Six months later, the project had expanded to more than 30 pages, each with its own CSS file. The button styles were inconsistent, the table components lacked functionality, and he had implemented the date picker from scratch three times. He spent a week researching mainstream UI component libraries.
In the end, Tom chose Ant Design for three reasons: First, Ant Design offers the most comprehensive set of components—enterprise-grade components such as tables, forms, and date pickers are ready to use right out of the box; second, it supports on-demand loading and tree shaking, so it doesn’t bloat the project; third, the Next.js integration is already well-established, and the community has a wealth of practical examples. He also used Headless UI to customize some special components not available in Ant Design.
Tom’s selection process is very informative: he first spent a week integrating three libraries into a test project and evaluated them based on three criteria—development efficiency, performance impact, and learning curve. Ant Design came out on top across the board in an enterprise backend scenario. This “test first, then decide” approach can also be applied to your own project selection process.
(1) Comparison of Component Library Options
| for Comparison | Ant Design | Material UI | Headless UI |
|---|---|---|---|
| Design Language | Enterprise-Level Back-End | Google Material Design | No Styling (Behavioral Logic) |
| Number of components | 60+ | 50+ | ~15 |
| Theme Customization | ConfigProvider + token | ThemeProvider + sx prop | Fully Customizable (Tailwind/CSS) |
| Styling Options | CSS-in-JS (v5) | CSS-in-JS (Emotion) | No Built-in Styles |
| Internationalization | 50+ built-in languages | 30+ built-in languages | None built-in |
| Use Cases | Backend Management, Enterprise Applications | Consumer-Facing Applications | Highly Customized Projects |
| Tree Shaking | ✅ Automatic | ✅ Automatic | ✅ Automatic |
| Learning Curve | Moderate | Moderate | Low (requires custom styling) |
When selecting a UI component library, you should consider three key factors: component comprehensiveness (whether it covers your business needs), customization flexibility (whether it meets designers’ requirements), and performance impact (whether it slows down the first-screen load).
Ant Design (antd) is the most popular React UI library in China, open-sourced by Ant Financial. Its design language is geared toward enterprise-level back-end and middle-tier applications, providing a full suite of components ranging from buttons and tables to date pickers and tree controls. The interaction logic between components has been extensively tested, making them ready for use in complex scenarios—such as pagination, sorting, and filter synchronization in data tables.
Material UI (MUI) is a React implementation of Google’s Material Design guidelines. Its strengths lie in its mature design guidelines, modern visual style, and robust theme system. It is well-suited for consumer-facing front-end applications and designer-led teams.
Headless UI differs from the two categories above—it does not provide any predefined styles, but only provides behavioral logic (accessibility, keyboard navigation, focus management). Developers are entirely responsible for implementing the styling themselves using Tailwind CSS or CSS Modules. It is suitable for projects that require a highly customized visual style.
▶ Example 1: Comparison of Button Implementations Across Component Libraries
Output:
Side-by-side comparison: HTML syntax vs JSX syntax showing equivalent markup. UI library component with pre-built styling and behavior
// === Ant Design button ===
// Ready to Use Out of the Box,Built-in Styles,Through type Switch Visual Styles by Attribute
import { Button, Space } from 'antd'
function AntDButtons() {
return (
<Space wrap>
<Button type="primary">Main Buttons</Button>
<Button>Default Button</Button>
<Button type="dashed">Dotted-line button</Button>
<Button type="link">Link Button</Button>
<Button type="primary" danger>Danger Button</Button>
<Button loading>Loading...</Button>
<Button type="primary" icon={<SearchOutlined />}>Search</Button>
</Space>
)
}
// === Material UI Button ===
// Through variant and color Combining Properties to Achieve Different Styles
import Button from '@mui/material/Button'
import Stack from '@mui/material/Stack'
import SaveIcon from '@mui/icons-material/Save'
function MUIButtons() {
return (
<Stack direction="row" spacing={2}>
<Button variant="contained">Fill Button</Button>
<Button variant="outlined">Outline Button</Button>
<Button variant="text">Text Button</Button>
<Button variant="contained" color="error">Danger</Button>
<Button variant="contained" disabled>Disable</Button>
<Button variant="contained" startIcon={<SaveIcon />}>Save</Button>
</Stack>
)
}
// === Headless UI + Tailwind ===
// Unstyled Components + Custom Class
import { Button as HeadlessButton } from '@headlessui/react'
function HeadlessButtons() {
return (
<div className="flex gap-2">
<HeadlessButton className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-500 data-[active]:bg-blue-700">
Main Buttons
</HeadlessButton>
<HeadlessButton className="rounded border border-gray-300 px-4 py-2 text-gray-700 hover:bg-gray-50">
Default Button
</HeadlessButton>
<HeadlessButton className="rounded bg-red-600 px-4 py-2 text-white hover:bg-red-500">
Danger Button
</HeadlessButton>
</div>
)
}
Output:
Tailwind: className="flex gap-4 p-6 rounded-lg shadow" → utility classes compose inline. No custom CSS needed.
(2) Integrating Ant Design with Next.js
When integrating Ant Design into Next.js, several key issues must be addressed: CSS-in-JS compatibility, on-demand loading, and client component boundaries. Ant Design v5 uses the CSS-in-JS (cssinjs) approach, which requires configuration in Next.js to ensure styles are injected correctly.
| Integration Issues | Cause | Solution |
|---|---|---|
| CSS-in-JS SSR Compatibility | CSS-in-JS Styles Cannot Be Injected on the Server Side | Wrap Client Component Boundaries with AntdProvider |
| First-screen size too large | The entire antd library is bundled into the initial JS file | dynamic() Dynamic import + optimizePackageImports |
| Server Component Error | antd components depend on browser APIs | Add 'use client' at the top of the file using the component |
| Flash of Unstyled Content (FOUC) | No styles on the server side; delayed injection on the client side | Extract critical CSS or use the App component for centralized management |
| Theme switching flickers | Theme token is loaded only on the client side | Set the default theme in ConfigProvider and inject it via SSR |
To optimize performance, components should use dynamic imports (dynamic) for on-demand loading to avoid bundling the entire component library into the above-the-fold JavaScript. Ant Design’s Tree Shaking takes effect automatically when building with ES Modules—simply import directly from antd; no configuration of babel-plugin-import is required.
▶ Example 2: Full Integration of Ant Design and Next.js
Output:
Displays: "Main Buttons"
// app/providers.tsx - Ant Design Themes and Configuration Provider
'use client'
import { useState } from 'react'
import { ConfigProvider, theme, App } from 'antd'
import zhCN from 'antd/locale/zh_CN'
export function AntdProvider({ children }: { children: React.ReactNode }) {
const [isDark] = useState(false)
return (
<ConfigProvider
locale={zhCN}
theme={{
// Switch to Light Mode/Dark Theme
algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm,
// Custom Theme Colors
token: {
colorPrimary: '#1677ff',
borderRadius: 6,
colorBgContainer: isDark ? '#141414' : '#ffffff',
},
}}
>
<App>{children}</App>
</ConfigProvider>
)
}
// app/layout.tsx - Global Import AntdProvider
import { AntdProvider } from './providers'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh">
<body>
<AntdProvider>{children}</AntdProvider>
</body>
</html>
)
}
// app/dashboard/page.tsx - Use dynamic import to load on demand Ant Design Components
// Avoid putting the entire antd Pack into the first screen JS in
import dynamic from 'next/dynamic'
// Dynamic Import(Load only when needed)
const DataTable = dynamic(() => import('@/components/DataTable'), {
loading: () => <div>Loading the table......</div>,
})
const DatePicker = dynamic(() => import('antd').then(mod => mod.DatePicker), {
loading: () => <div>Loading the date picker......</div>,
})
async function DashboardPage() {
// Analog Data Acquisition
const data = await fetch('https://api.example.com/table-data').then(r => r.json())
return (
<div style={{ padding: 24 }}>
<h1>Management Dashboard</h1>
<div style={{ marginBottom: 16 }}>
<DatePicker />
</div>
<DataTable data={data} />
</div>
)
}
export default DashboardPage
// components/DataTable.tsx - Complete Table Component
'use client'
import { Table, Tag, Space, Button, Popconfirm } from 'antd'
import type { ColumnsType } from 'antd/es/table'
interface UserData {
key: number
name: string
age: number
address: string
status: 'active' | 'inactive'
}
function DataTable({ data }: { data: UserData[] }) {
const columns: ColumnsType<UserData> = [
{ title: 'Name', dataIndex: 'name', key: 'name', sorter: (a, b) => a.name.localeCompare(b.name) },
{ title: 'Age', dataIndex: 'age', key: 'age', sorter: (a, b) => a.age - b.age },
{ title: 'Address', dataIndex: 'address', key: 'address' },
{
title: 'Status',
dataIndex: 'status',
key: 'status',
render: (status: string) => (
<Tag color={status === 'active' ? 'green' : 'red'}>
{status === 'active' ? 'Enable' : 'Disable'}
</Tag>
),
},
{
title: 'Operation',
key: 'action',
render: (_: any, record: UserData) => (
<Space>
<Button type="link" onClick={() => console.log('Edit', record.key)}>Edit</Button>
<Popconfirm title="Confirm Deletion?" onConfirm={() => console.log('Delete', record.key)}>
<Button type="link" danger>Delete</Button>
</Popconfirm>
</Space>
),
},
]
return (
<Table
columns={columns}
dataSource={data}
pagination={{ pageSize: 10, showSizeChanger: true, showTotal: (total) => `Total ${total} items` }}
bordered
size="middle"
/>
)
}
export default DataTable
Output:
State: selected, query
(3) Headless UI + Tailwind CSS customization
Headless UI offers a completely different approach to component libraries—style-free components. It exposes behavioral logic (such as closing an open dialog by pressing the Escape key, keyboard navigation for combo boxes, and animation management for transitions), while leaving styling entirely up to the developer. This means your UI can perfectly match the design mockups without needing to override third-party default styles.
Tom encountered some scenarios that Ant Design didn’t cover: a custom filter panel that required specific interactions, and a drop-down suggestion box for search results that needed special styling. These can be implemented very flexibly using Headless UI. Combined with Tailwind CSS utility classes, you can build a UI that matches the design mockups without writing a single line of custom CSS.
| Headless UI Components | Provided Behavior Logic | Parts Requiring Customization |
|---|---|---|
| Dialog | Escape: Close, Lock Focus, Disable Background Scrolling | Appearance, Animation, Mask Style |
| Combo Box | Keyboard Navigation, Search and Filter, Option Highlighting | Input Fields, Drop-down Panels, Option Styles |
| Menu | Navigate with arrow keys; click outside to close | Menu items, icons, and divider styles |
| Switch | Toggle State, Accessibility (ARIA) | Switch Track, Slider Style |
| Tab Group | Arrow Key Navigation, Panel Synchronization | Tab and Panel Layout Styles |
| Transition | Fade-In/Fade-Out Animation Management | Animation CSS Class Names |
▶ Example 3: Custom Headless UI Components
Output:
... ('Edit', record.key)
... ('Delete', record.key)
// components/SearchCombobox.tsx
// Usage Headless UI 's Combobox Implementing a search suggestion drop-down menu
'use client'
import { useState } from 'react'
import {
Combobox,
ComboboxInput,
ComboboxButton,
ComboboxOptions,
ComboboxOption,
Transition,
} from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
const people = [
{ id: 1, name: 'Alice', role: 'Admin' },
{ id: 2, name: 'Bob', role: 'Editor' },
{ id: 3, name: 'Charlie', role: 'Author' },
{ id: 4, name: 'Diana', role: 'Reader' },
{ id: 5, name: 'Eve', role: 'Admin' },
]
function SearchCombobox() {
const [selected, setSelected] = useState(people[0])
const [query, setQuery] = useState('')
const filtered = query === ''
? people
: people.filter((person) =>
person.name.toLowerCase().includes(query.toLowerCase())
)
return (
<div className="w-72">
<Combobox value={selected} onChange={setSelected}>
<div className="relative">
<ComboboxInput
className="w-full rounded-lg border border-gray-300 bg-white py-2 pl-3 pr-10 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
displayValue={(person: any) => person?.name}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search Users..."
/>
<ComboboxButton className="absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronDownIcon className="h-5 w-5 text-gray-400" />
</ComboboxButton>
</div>
<Transition
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<ComboboxOptions className="absolute z-10 mt-1 max-h-60 w-72 overflow-auto rounded-lg bg-white py-1 shadow-lg ring-1 ring-black/5">
{filtered.length === 0 && query !== '' ? (
<div className="px-3 py-2 text-sm text-gray-500">No matching results found</div>
) : (
filtered.map((person) => (
<ComboboxOption
key={person.id}
value={person}
className="cursor-pointer px-3 py-2 text-sm data-[focus]:bg-blue-100 data-[selected]:bg-blue-50"
>
{({ selected }) => (
<div className="flex justify-between">
<span className={selected ? 'font-medium' : ''}>{person.name}</span>
<span className="text-gray-400">{person.role}</span>
</div>
)}
</ComboboxOption>
))
)}
</ComboboxOptions>
</Transition>
</Combobox>
{selected && (
<p className="mt-2 text-sm text-gray-600">
Selected:{selected.name}({selected.role})
</p>
)}
</div>
)
}
export default SearchCombobox
Output:
Unknown URL → fallback "404: Page Not Found" route. Catch-all path="*" renders Not Found component.
▶ Example 4: Performance Optimization Configuration in next.config.ts
Output:
Displays: "person.name.toLowerCase().includes(query.toLowerCase()) ". State: selected (setter: setSelected), query (setter: setQuery)
// next.config.ts - Performance Optimization Configuration for the Component Library
import type { NextConfig } from 'next'
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
const nextConfig: NextConfig = withBundleAnalyzer({
optimizePackageImports: [
'antd',
'@ant-design/icons',
'@mui/material',
'@mui/icons-material',
'@headlessui/react',
],
transpilePackages: ['antd'],
experimental: {
optimizeCss: true,
},
})
export default nextConfig
// package.json scripts:
// "analyze": "ANALYZE=true next build"
// "analyze:server": "ANALYZE=true BUNDLE_ANALYZE=server next build"
// "analyze:browser": "ANALYZE=true BUNDLE_ANALYZE=browser next build"
Output:
next.config.js optimization: images.domains, react.strictMode, compiler.removeConsole, modularizeImports for tree-shaking
▶ Example 5: Strategies for Mixing and Matching Multiple Component Libraries
Output:
UI library component with pre-built styling and behavior
// components/HybridTable.tsx - Ant Design Table + Headless UI Custom Filters
'use client'
import { useState } from 'react'
import { Table, Tag } from 'antd'
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'
import type { ColumnsType } from 'antd/es/table'
interface Product {
key: string
name: string
price: number
category: string
status: 'in_stock' | 'out_of_stock'
}
const products: Product[] = [
{ key: '1', name: 'Laptop', price: 999, category: 'Electronics', status: 'in_stock' },
{ key: '2', name: 'Desk Chair', price: 299, category: 'Furniture', status: 'out_of_stock' },
{ key: '3', name: 'Coffee Maker', price: 79, category: 'Kitchen', status: 'in_stock' },
]
function HybridTable() {
const [categoryFilter, setCategoryFilter] = useState<string>('all')
const filtered = categoryFilter === 'all'
? products
: products.filter(p => p.category === categoryFilter)
const columns: ColumnsType<Product> = [
{ title: 'Name', dataIndex: 'name', key: 'name' },
{ title: 'Price', dataIndex: 'price', key: 'price', render: (v: number) => `$${v}` },
{ title: 'Category', dataIndex: 'category', key: 'category' },
{
title: 'Status',
dataIndex: 'status',
key: 'status',
render: (s: string) => (
<Tag color={s === 'in_stock' ? 'green' : 'red'}>
{s === 'in_stock' ? 'In Stock' : 'Out of Stock'}
</Tag>
),
},
]
return (
<div>
<div className="mb-4 flex items-center gap-2">
<Popover className="relative">
<PopoverButton className="rounded border px-3 py-1.5 text-sm">
Filter: {categoryFilter === 'all' ? 'All' : categoryFilter}
</PopoverButton>
<PopoverPanel className="absolute z-10 mt-1 w-40 rounded bg-white py-1 shadow-lg">
{['all', 'Electronics', 'Furniture', 'Kitchen'].map(cat => (
<button
key={cat}
className="block w-full px-3 py-1.5 text-left text-sm hover:bg-blue-50"
onClick={() => setCategoryFilter(cat)}
>
{cat === 'all' ? 'All Categories' : cat}
</button>
))}
</PopoverPanel>
</Popover>
</div>
<Table columns={columns} dataSource={filtered} pagination={false} size="small" />
</div>
)
}
export default HybridTable
Output:
Tailwind: className="flex gap-4 p-6 rounded-lg shadow" → utility classes compose inline. No custom CSS needed.
❓ FAQ
'use client'); they cannot be used directly in Server Components; Second, use dynamic to dynamically import components to avoid bundling the entire antd library into the above-the-fold JavaScript; third, configure themes and internationalization in ConfigProvider to ensure global style consistency. Ant Design v5 no longer requires babel-plugin-import, as Tree Shaking is automatically enabled.dynamic to load components on demand (dynamic(() => import('antd').then(m => m.Button))); configure next.config.ts’s optimizePackageImports; import only the components you need (import { Button } from 'antd' instead of importing everything); analyze the bundle size (@next/bundle-analyzer) to identify abnormal dependencies. After optimization, the impact of integrating Ant Design on the first-screen JavaScript is typically no more than 30KB.ConfigProvider and Material UI’s ThemeProvider provide comprehensive theme systems that allow you to override tokens such as colors, spacing, rounded corners, and fonts. Use CSS Modules or Tailwind to supplement styles that the component library’s theme system cannot cover. Avoid using !important to override component library styles—you should achieve this by using theme tokens or overriding CSS variables.babel-plugin-import. Additionally, v5 introduces the App component (which centrally manages static methods such as message, notification, and modal), the theme object (for fine-grained token customization), and improved SSR support. The TypeScript type definitions in v5 are also more comprehensive. If you’re upgrading from v4, be sure to pay attention to CSS-in-JS compatibility and changes to the theme API.📖 Summary
- Ant Design is suitable for enterprise-level backend and middleware systems; it offers a comprehensive set of components, the best React ecosystem, and mature integration solutions for Next.js.
- Material UI is designed for consumer-facing apps, features a mature design system, a robust theme system, and a modern visual style
- Headless UI provides style-free behavioral components, making it suitable for highly customized projects; it is often used in conjunction with Tailwind CSS
- Best Practices for Ant Design in Next.js:
'use client'Declaration +dynamicDynamic Import +ConfigProviderTheme Configuration - Tree Shaking works automatically with ES Modules; no additional configuration of
babel-plugin-importis required. - The
next.config.tsoptimization foroptimizePackageImportscan reduce the package size of the component library - Three Key Factors for Selecting a Component Library: Project Type (Backend/Frontend), Customization Requirements (Standard/Highly Customized), and Team Background (React/Design-Driven)
- Multiple component libraries can be used together: Ant Design provides standard components, while Headless UI supplements them with custom components
- Ant Design v5 uses a CSS-in-JS approach, so the Less compiler and babel-plugin-import are no longer required.
optimizePackageImportsConfigure settings to optimize the package size of the component library and reduce the amount of JavaScript loaded on the first screen- The Transition component in Headless UI, when used with Tailwind, can create smooth entrance and exit animations.
- When selecting a component library, it should first be piloted in a test project and evaluated based on three criteria: development efficiency, performance impact, and learning curve.
- This lesson covers the UI fundamentals for the integrated project (SaaS Kanban) in the next lesson.
📝 Exercises
- Integrate Ant Design into the blog’s admin interface: Create a
providers.tsxtheme configured for Ant Design and internationalization (set to Chinese), and wrap it in the global layoutAntdProvider. Use components such asButton,Table,Tag, andSpaceto redesign the blog’s user management page in the admin interface. - Use the
Comboboxcomponent from Headless UI + Tailwind CSS to implement a user search selector: retrieve a list of users from the API, support keyword search and filtering, and display user information when a user is selected. Compare this with Ant Design’sSelectcomponent to experience the level of customization flexibility offered by Headless UI. - Optimize the component library’s performance for the project: Use
@next/bundle-analyzerto analyze changes in package size before and after integrating Ant Design. Switch at least three large components (DatePicker, Table, TreeSelect) to dynamic loading usingdynamic. AddoptimizePackageImports: ['antd', '@ant-design/icons']tonext.config.tsand compare the first-screen JavaScript size and Lighthouse performance scores before and after optimization.