react-composables
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAccessibility
无障碍访问
URL: /accessibility
title: Accessibility
description: Building components that are usable by everyone, including users with disabilities who rely on assistive technologies.
Accessibility (a11y) is not an optional feature—it's a fundamental requirement for modern web components. Every component must be usable by everyone, including people with visual, motor, auditory, or cognitive disabilities.
This guide is a non-exhaustive list of accessibility principles and patterns that you should follow when building components. It's not a comprehensive guide, but it should give you a sense of the types of issues you should be aware of.
If you use a linter with strong accessibility rules like Ultracite, these types of issues will likely be caught automatically, but it's still important to understand the principles.
URL: /accessibility
title: 无障碍访问
description: 构建所有人都能使用的组件,包括依赖辅助技术的残障用户。
无障碍访问(a11y)并非可选功能,而是现代Web组件的基本要求。每个组件都必须能被所有人使用,包括有视觉、运动、听觉或认知障碍的人群。
本指南列出了构建组件时应遵循的无障碍原则和模式,但并非详尽无遗。它虽不是全面指南,但能让你了解需要关注的各类问题。
如果你使用带有严格无障碍规则的代码检查工具(如Ultracite),这类问题可能会被自动检测出来,但理解相关原则仍然很重要。
Core Principles
核心原则
- Semantic HTML First - Use native elements (,
<button>,<nav>) for built-in accessibility<ul> - Keyboard Navigation - Support Tab, Arrow keys, Home/End, Escape, Enter/Space for all interactions
- Screen Reader Support - Use ARIA attributes (,
aria-label,aria-current) for proper announcementsaria-live - Visual Accessibility - Ensure focus indicators, sufficient contrast (4.5:1), and responsive text sizing
- 优先使用语义化HTML - 使用原生元素(、
<button>、<nav>)来获得内置的无障碍支持<ul> - 键盘导航 - 支持Tab、方向键、Home/End、Escape、Enter/Space等所有交互操作
- 屏幕阅读器支持 - 使用ARIA属性(、
aria-label、aria-current)实现正确的语音播报aria-live - 视觉无障碍 - 确保焦点指示器、足够的对比度(4.5:1)和响应式文本大小
ARIA Patterns
ARIA模式
ARIA enhances semantic HTML for assistive technologies. Key rules:
- Use semantic HTML first, ARIA only when necessary
- Don't override native semantics
- All interactive elements need keyboard access and accessible names
Common Attributes:
- Roles - Define element type (,
role="button",role="navigation")role="alert" - States - Describe current state (,
aria-checked,aria-expanded)aria-selected - Properties - Provide context (,
aria-label,aria-describedby,aria-controls,aria-required)aria-invalid
ARIA可增强语义化HTML对辅助技术的支持。核心规则:
- 优先使用语义化HTML,仅在必要时使用ARIA
- 不要覆盖原生语义
- 所有交互元素都需要支持键盘访问,并具备无障碍名称
常用属性:
- Roles(角色) - 定义元素类型(、
role="button"、role="navigation")role="alert" - States(状态) - 描述当前状态(、
aria-checked、aria-expanded)aria-selected - Properties(属性) - 提供上下文信息(、
aria-label、aria-describedby、aria-controls、aria-required)aria-invalid
Component Patterns
组件模式
Complex interactive components require specific accessibility patterns. For detailed implementations, consult WAI-ARIA Authoring Practices.
Modal/Dialog:
- ,
role="dialog",aria-modal="true"aria-labelledby - Trap focus with Tab, close with Escape
- Store and restore previous focus
- Prevent body scroll when open
Dropdown Menu:
- on container,
role="menu"on itemsrole="menuitem" - ,
aria-haspopup="true",aria-expandedaria-controls - Arrow keys navigate, Enter/Space select, Escape closes
Tabs:
- on container,
role="tablist"on buttons,role="tab"on panelsrole="tabpanel" - ,
aria-selected,aria-controlsaria-labelledby - Arrow Left/Right navigate, Home/End jump to first/last
- Only active tab is focusable ()
tabIndex={0/-1}
Forms:
- paired with input
<label htmlFor>id - ,
aria-required,aria-invalidfor validationaria-describedby - Error messages with
role="alert" - Group related inputs with and
<fieldset><legend>
复杂的交互组件需要特定的无障碍模式。如需详细实现方案,请参考WAI-ARIA 作者实践指南。
模态框/对话框:
- 使用、
role="dialog"、aria-modal="true"aria-labelledby - 通过Tab键捕获焦点,Escape键关闭
- 保存并恢复之前的焦点
- 打开时禁止页面滚动
下拉菜单:
- 容器使用,菜单项使用
role="menu"role="menuitem" - 使用、
aria-haspopup="true"、aria-expandedaria-controls - 方向键导航,Enter/Space选择,Escape关闭
标签页:
- 容器使用,按钮使用
role="tablist",面板使用role="tab"role="tabpanel" - 使用、
aria-selected、aria-controlsaria-labelledby - 左右方向键导航,Home/End跳转到第一个/最后一个标签
- 仅激活的标签可获得焦点()
tabIndex={0/-1}
表单:
- 与输入框的
<label htmlFor>配对使用id - 使用、
aria-required、aria-invalid实现验证aria-describedby - 错误消息使用
role="alert" - 使用和
<fieldset>对相关输入框进行分组<legend>
Focus Management
焦点管理
- Focus Visible - Use for keyboard-only focus indicators
:focus-visible - Focus Trapping - Trap Tab/Shift+Tab within modals by cycling between first and last focusable elements
- Focus Restoration - Store before opening overlays, restore on close
document.activeElement
- 可见焦点 - 使用为仅通过键盘操作的焦点添加指示器
:focus-visible - 焦点捕获 - 在模态框内循环切换第一个和最后一个可聚焦元素,限制Tab/Shift+Tab的范围
- 焦点恢复 - 打开弹窗前保存,关闭时恢复焦点
document.activeElement
Live Regions
实时区域
Announce dynamic content changes to screen readers:
- Status Messages - (waits),
aria-live="polite"(interrupts),aria-live="assertive"for errorsrole="alert" - Progress - with
role="progressbar",aria-valuenow,aria-valuemin,aria-valuemaxaria-label
向屏幕阅读器播报动态内容变化:
- 状态消息 - (等待播报)、
aria-live="polite"(中断当前播报),错误信息使用aria-live="assertive"role="alert" - 进度条 - 使用并搭配
role="progressbar"、aria-valuenow、aria-valuemin、aria-valuemaxaria-label
Color and Contrast
颜色与对比度
- Contrast Ratios - Normal text: 4.5:1, Large text (≥18pt/14pt bold): 3:1, Non-text (icons, borders): 3:1
- Color Independence - Never use color alone; combine with text, icons, or ARIA attributes
- 对比度比例 - 普通文本:4.5:1,大文本(≥18pt/14pt粗体):3:1,非文本元素(图标、边框):3:1
- 颜色独立性 - 绝不能仅依赖颜色区分信息;需结合文本、图标或ARIA属性
Mobile Accessibility
移动端无障碍
- Touch Targets - Minimum 44×44px (iOS) or 48×48dp (Android)
- Viewport - Allow zoom ()
<meta name="viewport" content="width=device-width, initial-scale=1">
- 触摸目标 - 最小尺寸为44×44px(iOS)或48×48dp(Android)
- 视口 - 允许缩放()
<meta name="viewport" content="width=device-width, initial-scale=1">
Common Pitfalls
常见误区
- Placeholder as Label - Use persistent , not disappearing placeholders
<label> - Empty Buttons - Icon buttons need or visually hidden text
aria-label - Disabled Elements - Use instead of
aria-disabledto keep focusability and explain whydisabled
- 用占位符作为标签 - 使用持久显示的,而非会消失的占位符
<label> - 空按钮 - 图标按钮需要或视觉隐藏的文本
aria-label - 禁用元素 - 使用替代
aria-disabled,以保持可聚焦性并解释禁用原因disabled
asChild
asChild
URL: /as-child
title: asChild
description: How to use the prop to render a custom element within the component.
asChildUnderstanding asChild
asChild理解asChild
asChildWhen is , instead of rendering its default DOM element, the component merges its props, behaviors, and event handlers with its immediate child element.
asChildtruetsx
// Without asChild: Creates wrapper
<Dialog.Trigger><button>Open</button></Dialog.Trigger>
// Output: <button data-state="closed"><button>Open</button></button>
// With asChild: Merges props
<Dialog.Trigger asChild><button>Open</button></Dialog.Trigger>
// Output: <button data-state="closed">Open</button>当设为时,组件不会渲染其默认DOM元素,而是将自身的属性、行为和事件处理程序与直接子元素合并。
asChildtruetsx
// 不使用asChild:创建包装器
<Dialog.Trigger><button>打开</button></Dialog.Trigger>
// 输出:<button data-state="closed"><button>打开</button></button>
// 使用asChild:合并属性
<Dialog.Trigger asChild><button>打开</button></Dialog.Trigger>
// 输出:<button data-state="closed">打开</button>How It Works
工作原理
Uses to clone the child and merge props (including event handlers) from both parent and child components. The enhanced child is returned with combined functionality.
React.cloneElement使用克隆子元素,并合并父组件和子组件的属性(包括事件处理程序)。最终返回增强后的子元素,兼具两者的功能。
React.cloneElementKey Benefits
核心优势
- Semantic HTML - Use the most appropriate element (links for navigation, buttons for actions)
- Clean DOM Structure - Eliminates wrapper elements and "wrapper hell"
- Design System Integration - Works seamlessly with existing component libraries
- Component Composition - Compose multiple behaviors onto a single element
- 语义化HTML - 使用最合适的元素(导航用链接,操作按钮用button)
- 简洁DOM结构 - 消除包装器元素和“包装器地狱”
- 设计系统集成 - 与现有组件库无缝协作
- 组件组合 - 将多种行为组合到单个元素上
Common Use Cases
常见使用场景
- Custom Triggers - Replace default triggers with custom components or links
- Accessible Navigation - Maintain semantic navigation elements
- Form Integration - Integrate with form libraries while preserving functionality
- 自定义触发器 - 用自定义组件或链接替换默认触发器
- 无障碍导航 - 保留语义化导航元素
- 表单集成 - 与表单库集成的同时保留功能
Best Practices
最佳实践
- Maintain Accessibility - Ensure child elements have proper semantics and ARIA attributes
- Document Support - Use JSDoc to document the prop in your component interfaces
asChild - Test Forwarding - Verify props are properly forwarded to child components
- Handle Edge Cases - Consider conditional rendering and dynamic children
- 保持无障碍特性 - 确保子元素具备正确的语义和ARIA属性
- 文档支持 - 在组件接口中使用JSDoc文档说明属性
asChild - 测试属性转发 - 验证属性是否正确转发给子组件
- 处理边缘情况 - 考虑条件渲染和动态子元素
Common Pitfalls
常见误区
- Not Spreading Props - Child components must spread to receive merged behavior
...props - Multiple Children - expects exactly one child element, not multiple
asChild - Fragment Children - Fragments are not valid, use actual HTML elements
- 未展开属性 - 子组件必须展开才能接收合并后的行为
...props - 多个子元素 - 要求恰好有一个子元素,而非多个
asChild - Fragment子元素 - Fragment无效,请使用实际HTML元素
Composition
组件组合
URL: /composition
title: Composition
description: The foundation of building modern UI components.
Composition, or composability, is the foundation of building modern UI components. It is one of the most powerful techniques for creating flexible, reusable components that can handle complex requirements without sacrificing API clarity.
Instead of cramming all functionality into a single component with dozens of props, composition distributes responsibility across multiple cooperating components.
Fernando gave a great talk about this at React Universe Conf 2025, where he shared his approach to rebuilding Slack's Message Composer as a composable component.
<Video src="https://www.youtube.com/watch?v=4KvbVq3Eg5w" />URL: /composition
title: 组件组合
description: 构建现代UI组件的基础。
组件组合(或可组合性)是构建现代UI组件的基础。它是创建灵活、可重用组件最强大的技术之一,能在不牺牲API清晰度的前提下处理复杂需求。
与其将所有功能塞进一个带有数十个属性的单一组件,不如通过组合将职责分配给多个协作组件。
Fernando在2025年React Universe大会上发表了精彩演讲,分享了他将Slack消息编辑器重构为可组合组件的方法。
<Video src="https://www.youtube.com/watch?v=4KvbVq3Eg5w" />Making a component composable
打造可组合组件
To make a component composable, you need to break it down into smaller, more focused components. For example, let's take this Accordion component:
tsx
import { Accordion } from '@/components/ui/accordion';
const data = [
{
title: 'Accordion 1',
content: 'Accordion 1 content',
},
{
title: 'Accordion 2',
content: 'Accordion 2 content',
},
{
title: 'Accordion 3',
content: 'Accordion 3 content',
},
];
return <Accordion data={data} />;While this Accordion component might seem simple, it's handling too many responsibilities. It's responsible for rendering the container, trigger and content; as well as handling the accordion state and data.
Customizing the styling of this component is difficult because it's tightly coupled. It likely requires global CSS overrides. Additionally, adding new functionality or tweaking the behavior requires modifying the component source code.
To solve this, we can break this down into smaller, more focused components.
要让组件具备可组合性,需要将其拆分为更小、更专注的组件。例如,我们来看这个Accordion组件:
tsx
import { Accordion } from '@/components/ui/accordion';
const data = [
{
title: 'Accordion 1',
content: 'Accordion 1 content',
},
{
title: 'Accordion 2',
content: 'Accordion 2 content',
},
{
title: 'Accordion 3',
content: 'Accordion 3 content',
},
];
return <Accordion data={data} />;虽然这个Accordion组件看起来简单,但它承担了太多职责:既要渲染容器、触发器和内容,还要处理折叠面板的状态和数据。
自定义该组件的样式非常困难,因为它耦合度很高,可能需要全局CSS覆盖。此外,添加新功能或调整行为需要修改组件源代码。
为了解决这个问题,我们可以将其拆分为更小、更专注的组件。
1. Root Component
1. Root组件
First, let's focus on the container - the component that holds everything together i.e. the trigger and content. This container doesn't need to know about the data, but it does need to keep track of the open state.
However, we also want this state to be accessible by child components. So, let's use the Context API to create a context for the open state.
Finally, to allow for modification of the element, we'll extend the default HTML attributes.
divWe'll call this component the "Root" component.
tsx
type AccordionProps = React.ComponentProps<'div'> & {
open: boolean;
setOpen: (open: boolean) => void;
};
const AccordionContext = createContext<AccordionProps>({
open: false,
setOpen: () => {},
});
export type AccordionRootProps = React.ComponentProps<'div'> & {
open: boolean;
setOpen: (open: boolean) => void;
};
export const Root = ({ children, open, setOpen, ...props }: AccordionRootProps) => (
<AccordionContext.Provider value={{ open, setOpen }}>
<div {...props}>{children}</div>
</AccordionContext.Provider>
);首先,我们关注容器——即容纳所有内容(触发器和内容)的组件。这个容器不需要了解数据,但需要跟踪展开状态。
不过,我们还希望子组件能访问这个状态。因此,我们可以使用Context API为展开状态创建一个上下文。
最后,为了允许修改元素,我们将扩展默认HTML属性。
div我们将这个组件称为“Root”组件。
tsx
type AccordionProps = React.ComponentProps<'div'> & {
open: boolean;
setOpen: (open: boolean) => void;
};
const AccordionContext = createContext<AccordionProps>({
open: false,
setOpen: () => {},
});
export type AccordionRootProps = React.ComponentProps<'div'> & {
open: boolean;
setOpen: (open: boolean) => void;
};
export const Root = ({ children, open, setOpen, ...props }: AccordionRootProps) => (
<AccordionContext.Provider value={{ open, setOpen }}>
<div {...props}>{children}</div>
</AccordionContext.Provider>
);2. Item Component
2. Item组件
The Item component is the element that contains the accordion item. It is simply a wrapper for each item in the accordion.
tsx
export type AccordionItemProps = React.ComponentProps<'div'>;
export const Item = (props: AccordionItemProps) => <div {...props} />;Item组件是容纳折叠面板项的元素,它只是折叠面板中每个项的包装器。
tsx
export type AccordionItemProps = React.ComponentProps<'div'>;
export const Item = (props: AccordionItemProps) => <div {...props} />;3. Trigger Component
3. Trigger组件
The Trigger component is the element that opens the accordion when activated. It is responsible for:
- Rendering as a button by default (can be customized with )
asChild - Handling click events to open the accordion
- Managing focus when accordion closes
- Providing proper ARIA attributes
Let's add this component to our Accordion component.
tsx
export type AccordionTriggerProps = React.ComponentProps<'button'> & {
asChild?: boolean;
};
export const Trigger = ({ asChild, ...props }: AccordionTriggerProps) => (
<AccordionContext.Consumer>
{({ open, setOpen }) => <button onClick={() => setOpen(!open)} {...props} />}
</AccordionContext.Consumer>
);Trigger组件是激活时打开折叠面板的元素,它负责:
- 默认渲染为按钮(可通过自定义)
asChild - 处理点击事件以打开折叠面板
- 折叠面板关闭时管理焦点
- 提供正确的ARIA属性
让我们将这个组件添加到Accordion组件中。
tsx
export type AccordionTriggerProps = React.ComponentProps<'button'> & {
asChild?: boolean;
};
export const Trigger = ({ asChild, ...props }: AccordionTriggerProps) => (
<AccordionContext.Consumer>
{({ open, setOpen }) => <button onClick={() => setOpen(!open)} {...props} />}
</AccordionContext.Consumer>
);4. Content Component
4. Content组件
The Content component is the element that contains the accordion content. It is responsible for:
- Rendering the content when the accordion is open
- Providing proper ARIA attributes
Let's add this component to our Accordion component.
tsx
export type AccordionContentProps = React.ComponentProps<'div'> & {
asChild?: boolean;
};
export const Content = ({ asChild, ...props }: AccordionContentProps) => (
<AccordionContext.Consumer>{({ open }) => <div {...props} />}</AccordionContext.Consumer>
);Content组件是容纳折叠面板内容的元素,它负责:
- 折叠面板展开时渲染内容
- 提供正确的ARIA属性
让我们将这个组件添加到Accordion组件中。
tsx
export type AccordionContentProps = React.ComponentProps<'div'> & {
asChild?: boolean;
};
export const Content = ({ asChild, ...props }: AccordionContentProps) => (
<AccordionContext.Consumer>{({ open }) => <div {...props} />}</AccordionContext.Consumer>
);5. Putting it all together
5. 组合所有组件
Now that we have all the components, we can put them together in our original file.
tsx
import * as Accordion from '@/components/ui/accordion';
const data = [
{
title: 'Accordion 1',
content: 'Accordion 1 content',
},
{
title: 'Accordion 2',
content: 'Accordion 2 content',
},
{
title: 'Accordion 3',
content: 'Accordion 3 content',
},
];
return (
<Accordion.Root open={false} setOpen={() => {}}>
{data.map((item) => (
<Accordion.Item key={item.title}>
<Accordion.Trigger>{item.title}</Accordion.Trigger>
<Accordion.Content>{item.content}</Accordion.Content>
</Accordion.Item>
))}
</Accordion.Root>
);现在我们有了所有组件,可以将它们组合到原始文件中。
tsx
import * as Accordion from '@/components/ui/accordion';
const data = [
{
title: 'Accordion 1',
content: 'Accordion 1 content',
},
{
title: 'Accordion 2',
content: 'Accordion 2 content',
},
{
title: 'Accordion 3',
content: 'Accordion 3 content',
},
];
return (
<Accordion.Root open={false} setOpen={() => {}}>
{data.map((item) => (
<Accordion.Item key={item.title}>
<Accordion.Trigger>{item.title}</Accordion.Trigger>
<Accordion.Content>{item.content}</Accordion.Content>
</Accordion.Item>
))}
</Accordion.Root>
);Naming Conventions
命名规范
When building composable components, consistent naming conventions are crucial for creating intuitive and predictable APIs. Both shadcn/ui and Radix UI follow established patterns that have become the de facto standard in the React ecosystem.
构建可组合组件时,一致的命名规范对于创建直观且可预测的API至关重要。shadcn/ui和Radix UI都遵循已成为React生态系统事实标准的模式。
Root Components
Root组件
The component serves as the main container that wraps all other sub-components. It typically manages shared state and context by providing a context to all child components.
Roottsx
<AccordionRoot>{/* Child components */}</AccordionRoot>Roottsx
<AccordionRoot>{/* 子组件 */}</AccordionRoot>Interactive Elements
交互元素
Interactive components that trigger actions or toggle states use descriptive names:
- - The element that initiates an action (opening, closing, toggling)
Trigger - - The element that contains the main content being shown/hidden
Content
tsx
<CollapsibleTrigger>Click to expand</CollapsibleTrigger>
<CollapsibleContent>
Hidden content revealed here
</CollapsibleContent>触发操作或切换状态的交互组件使用描述性名称:
- - 启动操作(打开、关闭、切换)的元素
Trigger - - 包含显示/隐藏的主要内容的元素
Content
tsx
<CollapsibleTrigger>点击展开</CollapsibleTrigger>
<CollapsibleContent>
此处显示隐藏内容
</CollapsibleContent>Content Structure
内容结构
For components with structured content areas, use semantic names that describe their purpose:
- - Top section containing titles or controls
Header - - Main content area
Body - - Bottom section for actions or metadata
Footer
tsx
<DialogHeader>
{/* Form title */}
</DialogHeader>
<DialogBody>
{/* Form content */}
</DialogBody>
<DialogFooter>
{/* Form footer */}
</DialogFooter>对于具有结构化内容区域的组件,使用描述其用途的语义化名称:
- - 包含标题或控件的顶部区域
Header - - 主要内容区域
Body - - 用于操作或元数据的底部区域
Footer
tsx
<DialogHeader>
{/* 表单标题 */}
</DialogHeader>
<DialogBody>
{/* 表单内容 */}
</DialogBody>
<DialogFooter>
{/* 表单页脚 */}
</DialogFooter>Informational Components
信息组件
Components that provide information or context use descriptive suffixes:
- - Primary heading or label
Title - - Supporting text or explanatory content
Description
tsx
<CardTitle>Project Statistics</CardTitle>
<CardDescription>
View your project's performance over time
</CardDescription>提供信息或上下文的组件使用描述性后缀:
- - 主标题或标签
Title - - 辅助文本或解释性内容
Description
tsx
<CardTitle>项目统计</CardTitle>
<CardDescription>
查看项目的长期表现
</CardDescription>Data Attributes
数据属性
URL: /data-attributes
title: Data Attributes
description: Add data attributes to expose component state and enable flexible styling.
Data attributes provide a way to expose component state and structure to consumers for styling. Use two patterns: for visual states and for component identification.
data-statedata-slotURL: /data-attributes
title: 数据属性
description: 添加数据属性以暴露组件状态并实现灵活样式。
数据属性为开发者提供了一种暴露组件状态和结构以进行样式定制的方式。使用两种模式:用于视觉状态,用于组件标识。
data-statedata-slotWhen Creating Components
创建组件时
Add attributes to expose component state:
data-state- Visual states (open/closed, active/inactive, loading)
- Layout states (orientation, side, alignment)
- Interaction states (disabled, hover, focus when styling children)
Add attributes for stable component identification:
data-slot- Use kebab-case naming ()
data-slot="submit-button" - Name reflects purpose, not implementation
- Provides stable selectors that won't break when internals change
添加属性以暴露组件状态:
data-state- 视觉状态(展开/收起、激活/未激活、加载中)
- 布局状态(方向、侧边、对齐方式)
- 交互状态(禁用、悬停、聚焦,用于子元素样式)
添加属性以实现稳定的组件标识:
data-slot- 使用短横线命名法()
data-slot="submit-button" - 名称反映用途,而非实现细节
- 提供稳定的选择器,不会因内部变更而失效
Decision Framework
决策框架
When creating a component, choose the appropriate API:
- - For states that affect styling (open/closed, loading, disabled)
data-state - - For component identity (stable targeting, parent-child relationships)
data-slot - - For variants, sizes, behavior configuration, and event handlers
props
A well-designed component combines all three: props for variants/behavior, data-state for conditional styling, and data-slot for stable targeting.
For comprehensive usage patterns and examples, see the Data Attribute Styling Patterns section in react.mdc, which covers:
- Styling with (Tailwind arbitrary variants)
data-state - Radix UI data attributes
- Using with
data-slotandhas-[]selectors[&_] - Global CSS patterns
- Naming conventions and best practices
创建组件时,选择合适的API:
- - 用于影响样式的状态(展开/收起、加载中、禁用)
data-state - - 用于组件标识(稳定定位、父子关系)
data-slot - - 用于变体、尺寸、行为配置和事件处理程序
props
设计良好的组件会结合这三者:props用于变体/行为,data-state用于条件样式,data-slot用于稳定定位。
如需全面的使用模式和示例,请查看react.mdc中的数据属性样式模式部分,其中涵盖:
- 使用进行样式定制(Tailwind任意变体)
data-state - Radix UI数据属性
- 将与
data-slot和has-[]选择器配合使用[&_] - 全局CSS模式
- 命名规范和最佳实践
Definitions
术语定义
URL: /definitions
title: Definitions
description: This page establishes precise terminology used throughout the specification. Terms are intentionally framework agnostic, but we will use React for examples.
URL: /definitions
title: 术语定义
description: 本页面确立了本规范中使用的精确术语。术语故意保持框架无关,但我们将使用React示例。
1. Artifact Taxonomy
1. 工件分类
1.1 Primitive
1.1 Primitive(基础组件)
A primitive (or, unstyled component) is the lowest‑level building block that provides behavior and accessibility without any styling.
Primitives are completely headless (i.e. unstyled) and encapsulate semantics, focus management, keyboard interaction, layering/portals, ARIA wiring, measurement, and similar concerns. They provide the behavioral foundation but require styling to become finished UI.
Examples:
- Radix UI Primitives (Dialog, Popover, Tooltip, etc.)
- React Aria Components
- Base UI
- Headless UI
Expectations:
- Completely unstyled (headless).
- Single responsibility; composable into styled components.
- Ships with exhaustive a11y behavior for its role.
- Versioning favors stability; breaking changes are rare and documented.
Primitive(即无样式组件)是最低层级的构建块,它提供行为和无障碍支持但不包含任何样式。
Primitive完全是无样式的(headless),封装了语义、焦点管理、键盘交互、分层/门户、ARIA配置、测量等相关逻辑。它提供行为基础,但需要添加样式才能成为可用的UI。
示例:
- Radix UI Primitives(Dialog、Popover、Tooltip等)
- React Aria Components
- Base UI
- Headless UI
预期特性:
- 完全无样式(headless)。
- 单一职责;可组合为带样式的组件。
- 附带与其角色匹配的完整无障碍行为。
- 版本注重稳定性;破坏性变更罕见且有文档说明。
1.2 Component
1.2 Component(组件)
A component is a styled, reusable UI unit that adds visual design to primitives or composes multiple elements to create complete, functional interface elements.
Components are still relatively low-level but include styling, making them immediately usable in applications. They typically wrap unstyled primitives with default visual design while remaining customizable.
Examples:
- shadcn/ui components (styled wrappers of Radix primitives)
- Material UI components
- Ant Design components
Expectations:
- Clear props API; supports controlled and uncontrolled usage where applicable.
- Includes default styling but remains override-friendly (classes, tokens, slots).
- Fully keyboard accessible and screen-reader friendly (inherits from primitives).
- Composable (children/slots, render props, or compound subcomponents).
- May be built from primitives or implement behavior directly with styling.
Component是带有样式的可重用UI单元,它为Primitive添加视觉设计,或组合多个元素以创建完整、功能齐全的界面元素。
Component仍属于相对低层级,但包含样式,可直接在应用中使用。它们通常用默认视觉设计包裹无样式Primitive,同时保持可定制性。
示例:
- shadcn/ui components(Radix primitives的样式包装器)
- Material UI components
- Ant Design components
预期特性:
- 清晰的props API;适用时支持受控和非受控用法。
- 包含默认样式,但易于覆盖(类、令牌、插槽)。
- 完全支持键盘访问和屏幕阅读器(继承自Primitive)。
- 可组合(子元素/插槽、渲染props或复合子组件)。
- 可基于Primitive构建,或直接通过样式实现行为。
1.3 Pattern
1.3 Pattern(模式)
Patterns are a specific composition of primitives or components that are used to solve a specific UI/UX problem.
Examples:
- Form validation with inline errors
- Confirming destructive actions
- Typeahead search
- Optimistic UI
Expectations.
- Describes behavior, a11y, keyboard map, and failure modes.
- May include reference implementations in multiple frameworks.
Pattern是Primitive或Component的特定组合,用于解决特定的UI/UX问题。
示例:
- 带内联错误的表单验证
- 确认破坏性操作
- 输入联想搜索
- 乐观UI
预期特性:
- 描述行为、无障碍特性、键盘映射和故障模式。
- 可能包含多框架的参考实现。
1.4 Block
1.4 Block(区块)
An opinionated, production-ready composition of components that solves a concrete interface use case (often product-specific) with content scaffolding. Blocks trade generality for speed of adoption.
Examples:
- Pricing table
- Auth screens
- Onboarding stepper
- AI chat panel
- Billing settings form
Expectations.
- Strong defaults, copy-paste friendly, easily branded/themed.
- Minimal logic beyond layout and orchestration; domain logic is stubbed via handlers.
- Accepts data via props; never hides data behind fetches without a documented adapter.
Block是Component的一种 opinionated(有主见的)、可直接用于生产环境的组合,它通过内容脚手架解决具体的界面用例(通常是产品特定的)。Block以通用性为代价换取采用速度。
示例:
- 定价表
- 认证页面
- 引导流程步骤器
- AI聊天面板
- 账单设置表单
预期特性:
- 强大的默认配置,易于复制粘贴,可轻松品牌化/主题化。
- 除布局和编排外,逻辑极少;领域逻辑通过处理程序存根实现。
- 通过props接收数据;若无文档化适配器,绝不会在内部隐藏数据获取逻辑。
1.5 Page
1.5 Page(页面)
A complete, single-route view composed of multiple blocks arranged to serve a specific user-facing purpose. Pages combine blocks into a cohesive layout that represents one destination in an application.
Examples:
- Landing page (hero block + features block + pricing block + footer block)
- Product detail page (image gallery block + product info block + reviews block)
- Dashboard page (stats block + chart block + activity feed block)
Expectations:
- Combines multiple blocks into a unified layout for a single route.
- Focuses on layout and block orchestration rather than component-level details.
- May include page-specific logic for data coordination between blocks.
- Self-contained for a single URL/route; not intended to be reused across routes.
Page是由多个Block组合而成的完整单路由视图,用于实现特定的用户面向目标。Page将Block组合成连贯的布局,代表应用中的一个目的地。
示例:
- 登录页面(hero区块 + 功能区块 + 定价区块 + 页脚区块)
- 产品详情页(图片画廊区块 + 产品信息区块 + 评论区块)
- 仪表盘页面(统计区块 + 图表区块 + 活动流区块)
预期特性:
- 将多个Block组合成单路由的统一布局。
- 专注于布局和Block编排,而非组件级细节。
- 可能包含页面特定逻辑,用于协调Block之间的数据。
- 针对单个URL/路由独立存在;不打算跨路由重用。
1.6 Template
1.6 Template(模板)
A multi-page collection or full-site scaffold that bundles pages, routing configuration, shared layouts, global providers, and project structure. Templates are complete starting points for entire applications or major application sections.
Examples:
- TailwindCSS Templates
- shadcnblocks Templates (full application shells)
- "SaaS starter" (auth pages + dashboard pages + settings pages + marketing pages)
- "E-commerce template" (storefront + product pages + checkout flow + admin pages)
Expectations:
- Includes multiple pages with routing/navigation structure.
- Provides global configuration (theme providers, auth context, layout shells).
- Opinionated project structure with clear conventions.
- Designed as a comprehensive starting point; fork and customize rather than import as dependency.
- May include build configuration, deployment setup, and development tooling.
Template是多页面集合或全站点脚手架,包含页面、路由配置、共享布局、全局提供者和项目结构。Template是整个应用或主要应用部分的完整起点。
示例:
- TailwindCSS Templates
- shadcnblocks Templates(完整应用框架)
- "SaaS启动模板"(认证页面 + 仪表盘页面 + 设置页面 + 营销页面)
- "电商模板"(商店首页 + 产品页面 + 结账流程 + 管理页面)
预期特性:
- 包含多个页面及路由/导航结构。
- 提供全局配置(主题提供者、认证上下文、布局框架)。
- 有主见的项目结构,具备清晰的规范。
- 设计为全面的起点;通过分叉和定制使用,而非作为依赖导入。
- 可能包含构建配置、部署设置和开发工具。
1.7 Utility (Non-visual)
1.7 Utility(工具,非视觉)
A helper exported for developer ergonomics or composition; not rendered UI.
Examples:
- React hooks (useControllableState, useId)
- Class utilities
- Keybinding helpers
- Focus scopes
Expectations.
- Side-effect free (except where explicitly documented).
- Testable in isolation; supports tree-shaking.
Utility是为提升开发者体验或组件组合而导出的辅助工具;并非可渲染的UI。
示例:
- React hooks(useControllableState、useId)
- 类工具
- 快捷键助手
- 焦点作用域
预期特性:
- 无副作用(除非有明确文档说明)。
- 可独立测试;支持摇树优化。
2. API and Composition Vocabulary
2. API与组合词汇
2.1 Props API
2.1 Props API
The public configuration surface of a component. Props are stable, typed, and documented with defaults and a11y ramifications.
组件的公共配置接口。Props是稳定的、带类型的,并附带默认值和无障碍影响的文档说明。
2.2 Children / Slots
2.2 Children / Slots(子元素/插槽)
Placeholders for caller-provided structure or content.
- Children (implicit slot). JSX between opening/closing tags.
- Named slots. Props like icon, footer, or subcomponents.
<Component.Slot> - Slot forwarding. Passing DOM attributes/className/refs through to the underlying element.
用于容纳调用者提供的结构或内容的占位符。
- Children(隐式插槽)。位于开闭标签之间的JSX。
- Named slots(命名插槽)。如icon、footer或子组件等props。
<Component.Slot> - Slot forwarding(插槽转发)。将DOM属性/className/refs传递给底层元素。
2.3 Render Prop (Function-as-Child)
2.3 Render Prop(函数作为子元素)
A function child used to delegate rendering while the parent supplies state/data.
tsx
<ParentComponent data={data}>
{(item) => <ChildComponent key={item.id} {...item} />}
</ParentComponent>Use when the parent must own data/behavior but the consumer must fully control markup.
作为子元素的函数,用于委托渲染,同时父组件提供状态/数据。
tsx
<ParentComponent data={data}>
{(item) => <ChildComponent key={item.id} {...item} />}
</ParentComponent>当父组件必须拥有数据/行为,但消费者必须完全控制标记时使用。
2.4 Controlled vs. Uncontrolled
2.4 Controlled vs. Uncontrolled(受控与非受控)
Controlled and uncontrolled are terms used to describe the state of a component.
Controlled components have their value driven by props, and typically emit an event (source of truth is the parent). Uncontrolled components hold internal state; and may expose a and imperative reset.
onChangedefaultValueMany inputs should support both. Learn more about controlled and uncontrolled state.
受控和非受控是描述组件状态的术语。
受控组件的值由props驱动,通常会触发事件(数据源是父组件)。非受控组件持有内部状态;可能会暴露和命令式重置方法。
onChangedefaultValue许多输入组件应同时支持这两种模式。了解更多关于受控和非受控状态的信息。
2.5 Provider / Context
2.5 Provider / Context(提供者/上下文)
A top-level component that supplies shared state/configuration to a subtree (e.g., theme, locale, active tab id). Providers are explicitly documented with required placement.
顶级组件,为子树提供共享状态/配置(例如主题、区域设置、活动标签ID)。提供者的放置位置有明确的文档说明。
2.6 Portal
2.6 Portal(门户)
Rendering UI outside the DOM hierarchy to manage layering/stacking context (e.g., modals, popovers, toasts), while preserving a11y (focus trap, aria-modal, inert background).
在DOM层级之外渲染UI,以管理分层/堆叠上下文(例如模态框、弹出框、提示框),同时保持无障碍特性(焦点捕获、aria-modal、背景惰性)。
3. Styling and Theming Vocabulary
3. 样式与主题词汇
3.1 Headless
3.1 Headless(无样式)
Implements behavior and accessibility without prescribing appearance. Requires the consumer to supply styling.
实现行为和无障碍支持,但不规定外观。需要消费者提供样式。
3.2 Styled
3.2 Styled(带样式)
Ships with default visual design (CSS classes, inline styles, or tokens) but remains override-friendly (className merge, CSS vars, theming).
附带默认视觉设计(CSS类、内联样式或令牌),但仍易于覆盖(类名合并、CSS变量、主题化)。
3.3 Variants
3.3 Variants(变体)
Discrete, documented style or behavior permutations exposed via props (e.g., , ). Variants are not separate components.
size="sm|md|lg"tone="neutral|destructive"通过props暴露的离散、有文档说明的样式或行为变体(例如、)。变体不是单独的组件。
size="sm|md|lg"tone="neutral|destructive"3.4 Design Tokens
3.4 Design Tokens(设计令牌)
Named, platform-agnostic values (e.g., , , ) that parameterize visual design and support theming.
--color-bg--radius-md--space-2命名的、平台无关的值(例如、、),用于参数化视觉设计并支持主题化。
--color-bg--radius-md--space-24. Accessibility Vocabulary
4. 无障碍词汇
4.1 Role / State / Property
4.1 Role / State / Property(角色/状态/属性)
WAI-ARIA attributes that communicate semantics (), state (), and relationships (, ).
role="menu"aria-checkedaria-controlsaria-labelledbyWAI-ARIA属性,用于传达语义()、状态()和关系(、)。
role="menu"aria-checkedaria-controlsaria-labelledby4.2 Keyboard Map
4.2 Keyboard Map(键盘映射)
The documented set of keyboard interactions for a widget (e.g., , , , ). Every interactive component declares and implements a keyboard map.
TabArrow keysHome/EndEscape小部件的已文档化键盘交互集合(例如、方向键、、)。每个交互组件都需声明并实现键盘映射。
TabHome/EndEscape4.3 Focus Management
4.3 Focus Management(焦点管理)
Rules for initial focus, roving focus, focus trapping, and focus return on teardown.
初始焦点、移动焦点、焦点捕获和销毁时焦点返回的规则。
5. Distribution Vocabulary
5. 分发词汇
5.1 Package (Registry Distribution)
5.1 Package(包,注册表分发)
The component/library is published to a package registry (e.g., ) and imported via a bundler. Favors versioned updates and dependency management.
npm组件/库发布到包注册表(例如),通过打包器导入。支持版本更新和依赖管理。
npm5.2 Copy-and-Paste (Source Distribution)
5.2 Copy-and-Paste(复制粘贴,源码分发)
Source code is integrated directly into the consumer's repository (often via a CLI). Favors ownership, customization, and zero extraneous runtime.
源码直接集成到消费者的仓库中(通常通过CLI)。支持所有权、定制化和零额外运行时。
5.3 Registry (Catalog)
5.3 Registry(注册表,目录)
A curated index of artifacts (primitives, components, blocks, templates) with metadata, previews, and install/copy instructions. A registry is not necessarily a package manager.
人工整理的工件索引(Primitive、Component、Block、Template),包含元数据、预览和安装/复制说明。注册表不一定是包管理器。
6. Classification Heuristics
6. 分类启发法
Use this decision flow to name and place an artifact:
- Does it encapsulate a single behavior or a11y concern, with no styling? → Primitive
- Is it a styled, reusable UI element that adds visual design to primitives or composes multiple elements? → Component
- Does it solve a concrete product use case with opinionated composition and copy? → Block
- Does it scaffold a page/flow with routing/providers and replaceable regions? → Template
- Is it documentation of a recurring solution, independent of implementation? → Pattern
- Is it non-visual logic for ergonomics/composition? → Utility
使用以下决策流程为工件命名和归类:
- 它是否封装了单一行为或无障碍关注点,且无样式? → Primitive
- 它是否是带样式的可重用UI元素,为Primitive添加视觉设计或组合多个元素? → Component
- 它是否通过有主见的组合和文案解决具体的产品用例? → Block
- 它是否通过路由/提供者和可替换区域搭建页面/流程? → Template
- 它是否是独立于实现的 recurring(重复出现)解决方案的文档? → Pattern
- 它是否是用于提升体验/组合的非视觉逻辑? → Utility
7. Non-Goals and Clarifications
7. 非目标与说明
- Web Components vs. "Components." In this spec, "component" refers to a reusable UI unit (examples in React). It does not imply the HTML Custom Elements standard unless explicitly stated. Equivalent principles apply across frameworks.
- Widgets. The term “widget” is avoided due to ambiguity; use component (general) or pattern (documentation-only solution).
- Themes vs. Styles. A theme is a parameterization of styles (via tokens). Styles are the concrete presentation. Components should support themes; blocks/templates may ship opinionated styles plus theming hooks.
- Web Components vs. "Components"。在本规范中,"component"指可重用的UI单元(React示例)。除非明确说明,否则不指代HTML自定义元素标准。等效原则适用于其他框架。
- Widgets。避免使用“widget”一词,因其含义模糊;使用component(通用)或pattern(仅文档化解决方案)。
- Themes vs. Styles。主题是样式的参数化(通过令牌)。样式是具体的呈现方式。Component应支持主题;Block/Template可能附带opinionated样式和主题化钩子。
Design Tokens
设计令牌
URL: /design-tokens
title: Design Tokens
description: How semantic naming conventions and design tokens create a flexible, maintainable theming system.
Design tokens are semantic CSS variables that separate theme, context, and usage concerns. Rather than hardcoding colors, use a semantic naming convention that creates layers of abstraction between what something is and how it looks.
This architectural decision creates a maintainable, flexible system that scales across applications.
For practical implementation and examples, see the Design Tokens section in react.mdc, which covers:
- Variable architecture and structure
- Common token patterns (,
--background,--foreground, etc.)--primary - Theme switching (light/dark modes)
- Usage in components
URL: /design-tokens
title: 设计令牌
description: 语义命名规范和设计令牌如何创建灵活、可维护的主题系统。
设计令牌是语义化CSS变量,用于分离主题、上下文和使用关注点。与其硬编码颜色,不如使用语义命名规范,在事物的本质和外观之间创建抽象层。
这种架构决策创建了一个可维护、灵活的系统,可跨应用扩展。
如需实际实现和示例,请查看react.mdc中的设计令牌部分,其中涵盖:
- 变量架构和结构
- 常见令牌模式(、
--background、--foreground等)--primary - 主题切换(亮色/暗色模式)
- 在组件中的使用
Overview
概述
URL: /
title: Overview
description: components.build is an open-source standard for building modern, composable and accessible UI components.
Modern web applications are built on reusable UI components and how we design, build, and share them is important. This specification aims to establish a formal, open standard for building open-source UI components for the modern web.
It is co-authored by <Author name="Hayden Bleasel" image="https://github.com/haydenbleasel.png" href="https://x.com/haydenbleasel" /> and <Author name="shadcn" image="https://github.com/shadcn.png" href="https://x.com/shadcn" />, with contributions from the open-source community and informed by popular projects in the React ecosystem.
The goal is to help open-source maintainers and senior front-end engineers create components that are composable, accessible, and easy to adopt across projects.
URL: /
title: 概述
description: components.build是用于构建现代、可组合且无障碍UI组件的开源标准。
现代Web应用基于可重用UI组件构建,我们设计、构建和共享它们的方式至关重要。本规范旨在为现代Web构建开源UI组件建立正式的开放标准。
它由<Author name="Hayden Bleasel" image="https://github.com/haydenbleasel.png" href="https://x.com/haydenbleasel" />和<Author name="shadcn" image="https://github.com/shadcn.png" href="https://x.com/shadcn" />共同编写,并有开源社区的贡献,同时参考了React生态系统中的热门项目。
目标是帮助开源维护者和资深前端工程师创建可组合、无障碍且易于跨项目采用的组件。
What is this specification?
本规范是什么?
This spec is not a tutorial or course on React, nor a promotion for any specific component library or registry. Instead, it provides high-level guidelines, best practices, and a common terminology for designing UI components.
By following this specification, developers can ensure their components are consistent with modern expectations and can integrate smoothly into any codebase.
本规范不是React教程或课程,也不是对任何特定组件库或注册表的推广。相反,它提供了设计UI组件的高层指南、最佳实践和通用术语。
遵循本规范,开发者可确保其组件符合现代预期,并能顺利集成到任何代码库中。
Who is this for?
面向人群
We're writing this for open-source maintainers and experienced front-end engineers who build and distribute component libraries or design systems. We assume you are familiar with JavaScript/TypeScript and React.
All examples will use React (with JSX/TSX) for concreteness, but we hope the fundamental concepts apply to other frameworks like Vue, Svelte, or Angular.
In other words, we hope this spec’s philosophy is framework-agnostic – whether you build with React or another library, you should emphasize the same principles of composition, accessibility, and maintainability.
我们为开源维护者和经验丰富的前端工程师编写本规范,他们负责构建和分发组件库或设计系统。我们假设你熟悉JavaScript/TypeScript和React。
所有示例将使用React(带JSX/TSX)以具体化,但我们希望核心概念适用于其他框架,如Vue、Svelte或Angular。
换句话说,我们希望本规范的理念是框架无关的——无论你使用React还是其他库,都应强调相同的组合、无障碍和可维护性原则。
Polymorphism
多态性
URL: /polymorphism
title: Polymorphism
description: How to use the prop to change the rendered HTML element while preserving component functionality.
asThe prop is a fundamental pattern in modern React component libraries that allows you to change the underlying HTML element or component that gets rendered.
asPopularized by libraries like Styled Components, Emotion, and Chakra UI, this pattern provides flexibility in choosing semantic HTML while maintaining component styling and behavior.
The prop enables polymorphic components - components that can render as different element types while preserving their core functionality:
astsx
<Button as="a" href="/home">
Go Home
</Button>
<Button as="button" type="submit">
Submit Form
</Button>
<Button as="div" role="button" tabIndex={0}>
Custom Element
</Button>URL: /polymorphism
title: 多态性
description: 如何使用属性更改渲染的HTML元素,同时保留组件功能。
asasastsx
<Button as="a" href="/home">
返回首页
</Button>
<Button as="button" type="submit">
提交表单
</Button>
<Button as="div" role="button" tabIndex={0}>
自定义元素
</Button>Understanding as
as理解as
asThe prop allows you to override the default element type of a component. Instead of being locked into a specific HTML element, you can adapt the component to render as any valid HTML tag or even another React component.
astsx
<Box>Content</Box> // Renders as default (div)
<Box as="section">Content</Box> // Renders as <section>
<Box as="nav">Content</Box> // Renders as <nav>astsx
<Box>内容</Box> // 默认渲染为div
<Box as="section">内容</Box> // 渲染为<section>
<Box as="nav">内容</Box> // 渲染为<nav>Implementation Methods
实现方法
There are two main approaches to implementing polymorphic components: a manual implementation and using Radix UI's component.
Slot实现多态组件主要有两种方法:手动实现和使用Radix UI的组件。
SlotManual Implementation
手动实现
The prop implementation uses dynamic component rendering:
astsx
// Simplified implementation
function Component({ as: Element = 'div', children, ...props }) {
return <Element {...props}>{children}</Element>;
}
// More complete implementation with TypeScript
type PolymorphicProps<E extends React.ElementType> = {
as?: E;
children?: React.ReactNode;
} & React.ComponentPropsWithoutRef<E>;
function Component<E extends React.ElementType = 'div'>({
as,
children,
...props
}: PolymorphicProps<E>) {
const Element = as || 'div';
return <Element {...props}>{children}</Element>;
}The component:
- Accepts an prop with a default element type
as - Uses the provided element or fallback to default
- Spreads all other props to the rendered element
- Maintains type safety with TypeScript generics
astsx
// 简化实现
function Component({ as: Element = 'div', children, ...props }) {
return <Element {...props}>{children}</Element>;
}
// 更完整的TypeScript实现
type PolymorphicProps<E extends React.ElementType> = {
as?: E;
children?: React.ReactNode;
} & React.ComponentPropsWithoutRef<E>;
function Component<E extends React.ElementType = 'div'>({
as,
children,
...props
}: PolymorphicProps<E>) {
const Element = as || 'div';
return <Element {...props}>{children}</Element>;
}该组件:
- 接受带有默认元素类型的属性
as - 使用提供的元素或回退到默认元素
- 将所有其他属性展开到渲染的元素
- 通过TypeScript泛型保持类型安全
Using Radix UI Slot
使用Radix UI Slot
Radix UI provides a component that offers a more powerful alternative to the prop pattern. Instead of just changing the element type, merges props with the child component, enabling composition patterns.
SlotasSlotFirst, install the package:
package
npm install @radix-ui/react-slotThe pattern uses a boolean prop instead of specifying the element type:
asChildtsx
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
const itemVariants = cva('rounded-lg border p-4', {
variants: {
variant: {
default: 'bg-white',
primary: 'bg-blue-500 text-white',
},
size: {
default: 'h-10 px-4',
sm: 'h-8 px-3',
lg: 'h-12 px-6',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
});
function Item({
className,
variant = 'default',
size = 'default',
asChild = false,
...props
}: React.ComponentProps<'div'> & VariantProps<typeof itemVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : 'div';
return (
<Comp
data-slot="item"
data-variant={variant}
data-size={size}
className={cn(itemVariants({ variant, size, className }))}
{...props}
/>
);
}Now you can use it in two ways:
tsx
// Default: renders as a div
<Item variant="primary">Content</Item>
// With asChild: merges props with child component
<Item variant="primary" asChild>
<a href="/home">Link with Item styles</a>
</Item>The component:
Slot- Clones the child element
- Merges the component's props (className, data attributes, etc.) with the child's props
- Forwards refs correctly
- Handles event handler composition
首先,安装包:
package
npm install @radix-ui/react-slotasChildtsx
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
const itemVariants = cva('rounded-lg border p-4', {
variants: {
variant: {
default: 'bg-white',
primary: 'bg-blue-500 text-white',
},
size: {
default: 'h-10 px-4',
sm: 'h-8 px-3',
lg: 'h-12 px-6',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
});
function Item({
className,
variant = 'default',
size = 'default',
asChild = false,
...props
}: React.ComponentProps<'div'> & VariantProps<typeof itemVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : 'div';
return (
<Comp
data-slot="item"
data-variant={variant}
data-size={size}
className={cn(itemVariants({ variant, size, className }))}
{...props}
/>
);
}现在你可以通过两种方式使用它:
tsx
// 默认:渲染为div
<Item variant="primary">内容</Item>
// 使用asChild:将属性与子组件合并
<Item variant="primary" asChild>
<a href="/home">带有Item样式的链接</a>
</Item>Slot- 克隆子元素
- 将组件的属性(className、数据属性等)与子元素的属性合并
- 正确转发refs
- 处理事件处理程序的组合
Comparison: as
vs asChild
asasChild对比:as
vs asChild
asasChildastsx
// Explicit element type
<Button as="a" href="/home">Link Button</Button>
<Button as="button" type="submit">Submit Button</Button>
// Simple, predictable API
// Limited to element typesasChildtsx
// Implicit from child
<Button asChild>
<a href="/home">Link Button</a>
</Button>
<Button asChild>
<button type="submit">Submit Button</button>
</Button>
// More flexible composition
// Works with any component
// Better prop mergingKey differences:
| Feature | | |
|---|---|---|
| API Style | | |
| Element Type | Specified in prop | Inferred from child |
| Component Composition | Limited | Full support |
| Prop Merging | Basic spread | Intelligent merging |
| Ref Forwarding | Manual setup needed | Built-in |
| Event Handlers | May conflict | Composed correctly |
| Library Size | No dependency | Requires |
astsx
// 显式指定元素类型
<Button as="a" href="/home">链接按钮</Button>
<Button as="button" type="submit">提交按钮</Button>
// API简单、可预测
// 仅限于元素类型asChildtsx
// 从子元素推断
<Button asChild>
<a href="/home">链接按钮</a>
</Button>
<Button asChild>
<button type="submit">提交按钮</button>
</Button>
// 组合更灵活
// 适用于任何组件
// 属性合并更智能主要区别:
| 特性 | | |
|---|---|---|
| API风格 | | |
| 元素类型 | 在属性中指定 | 从子元素推断 |
| 组件组合 | 受限 | 完全支持 |
| 属性合并 | 基础展开 | 智能合并 |
| Ref转发 | 需要手动设置 | 内置支持 |
| 事件处理程序 | 可能冲突 | 正确组合 |
| 库大小 | 无依赖 | 需要 |
When to Use Each Approach
何时使用每种方法
Use prop when:
as- You want a simpler API surface
- You're primarily switching between HTML elements
- You want to avoid additional dependencies
- The component is simple and doesn't need complex prop merging
Use + Slot when:
asChild- You need to compose with other components
- You want automatic prop merging behavior
- You're building a component library similar to Radix UI or shadcn/ui
- You need reliable ref forwarding across different component types
使用属性的场景:
as- 你希望API表面更简单
- 主要在HTML元素之间切换
- 希望避免额外依赖
- 组件简单,不需要复杂的属性合并
使用 + Slot的场景:
asChild- 需要与其他组件组合
- 希望自动属性合并行为
- 正在构建类似Radix UI或shadcn/ui的组件库
- 需要在不同组件类型之间可靠地转发refs
Key Benefits
核心优势
- Semantic HTML Flexibility - Use the most appropriate element (,
<Container as="nav">,<Container as="main">)<Container as="aside"> - Component Reusability - One component serves multiple purposes (,
<Text as="h1">,<Text as="p">)<Text as="label"> - Accessibility - Choose elements with best a11y for context (vs
<Button as="a" href="/">)<Button as="button"> - Style System Integration - Maintain consistent styling while changing elements
- 语义化HTML灵活性 - 使用最合适的元素(、
<Container as="nav">、<Container as="main">)<Container as="aside"> - 组件可重用性 - 一个组件可服务多种用途(、
<Text as="h1">、<Text as="p">)<Text as="label"> - 无障碍特性 - 根据上下文选择无障碍最佳的元素(vs
<Button as="a" href="/">)<Button as="button"> - 样式系统集成 - 更改元素的同时保持一致的样式
Common Use Cases
常见使用场景
- Typography - Flexible text components that can render as headings, paragraphs, labels, etc.
- Layout - Semantic layout components (Flex, Grid, Stack) that adapt to semantic containers
- Interactive - Components that handle buttons, links, and custom interactive elements with proper accessibility
- 排版 - 灵活的文本组件,可渲染为标题、段落、标签等
- 布局 - 语义化布局组件(Flex、Grid、Stack),可适配语义化容器
- 交互组件 - 处理按钮、链接和自定义交互元素的组件,具备正确的无障碍特性
TypeScript Best Practices
TypeScript最佳实践
Use generic types for full type safety:
tsx
type PolymorphicProps<E extends React.ElementType, Props = {}> = Props &
Omit<React.ComponentPropsWithoutRef<E>, keyof Props> & { as?: E };
function Component<E extends React.ElementType = 'div'>({
as,
...props
}: PolymorphicProps<E, { customProp?: string }>) {
const Element = as || 'div';
return <Element {...props} />;
}This enables automatic prop inference ( validates href, but errors).
<Component as="a" href="/"><Component as="div" href="/">使用泛型实现完整的类型安全:
tsx
type PolymorphicProps<E extends React.ElementType, Props = {}> = Props &
Omit<React.ComponentPropsWithoutRef<E>, keyof Props> & { as?: E };
function Component<E extends React.ElementType = 'div'>({
as,
...props
}: PolymorphicProps<E, { customProp?: string }>) {
const Element = as || 'div';
return <Element {...props} />;
}这实现了自动属性推断(会验证href,而会报错)。
<Component as="a" href="/"><Component as="div" href="/">Best Practices
最佳实践
- Default to semantic elements - Choose meaningful defaults (not
as: Element = 'article')'div' - Document valid elements - Use JSDoc and TypeScript unions to specify supported elements
- Validate element appropriateness - Warn in development when accessibility attributes are missing
- Handle event handlers properly - Add keyboard support when using non-button elements as clickable
- 默认使用语义化元素 - 选择有意义的默认值(而非
as: Element = 'article')'div' - 文档化有效元素 - 使用JSDoc和TypeScript联合类型指定支持的元素
- 验证元素适用性 - 在开发阶段,当缺少无障碍属性时发出警告
- 正确处理事件处理程序 - 使用非按钮元素作为可点击元素时,添加键盘支持
Common Pitfalls
常见误区
- Invalid HTML nesting - Avoid invalid combinations (button in button, div in p)
- Missing accessibility - Add ARIA labels when using semantic elements ()
<Box as="nav" aria-label="..."> - Type safety loss - Use generic types, not
any - Performance - Don't create components inline, define them outside the render function
- 无效HTML嵌套 - 避免无效组合(按钮内嵌套按钮,p内嵌套div)
- 缺少无障碍特性 - 使用语义化元素时添加ARIA标签()
<Box as="nav" aria-label="..."> - 丢失类型安全 - 使用泛型,而非
any - 性能问题 - 不要在渲染函数内联创建组件,在外部定义
Core Principles
核心原则
URL: /principles
title: Core Principles
description: When building modern UI components, it's important to keep these core principles in mind.
URL: /principles
title: 核心原则
description: 构建现代UI组件时,需要牢记这些核心原则。
Composability and Reusability
可组合性与可重用性
Favor composition over inheritance – build components that can be combined and nested to create more complex UIs, rather than relying on deep class hierarchies.
Composable components expose a clear API (via props/slots) that allows developers to customize behavior and appearance by plugging in child elements or callbacks.
This makes components highly reusable in different contexts. (React’s design reinforces this: “Props and composition give you all the flexibility you need to customize a component’s look and behavior in an explicit and safe way.”)
优先组合而非继承——构建可组合和嵌套以创建更复杂UI的组件,而非依赖深层类层次结构。
可组合组件通过props/slots暴露清晰的API,允许开发者通过插入子元素或回调来自定义行为和外观。
这使得组件在不同上下文中具有高度可重用性。(React的设计强化了这一点:“Props和组合为你提供了显式且安全地自定义组件外观和行为所需的所有灵活性。”)
Accessible by Default
默认无障碍
Components must be usable by all users. Use semantic HTML elements appropriate to the component’s role (e.g. for clickable actions, for lists, etc.) and augment with WAI-ARIA attributes when necessary.
<button><ul>/<li>Ensure keyboard navigation and focus management are supported (for example, arrow-key navigation in menus, focus traps in modals). Each component should adhere to accessibility standards and guidelines out of the box.
This means providing proper ARIA roles/states and testing with screen readers. Accessibility is not optional – it’s a baseline feature of every component.
组件必须能被所有用户使用。使用与组件角色匹配的语义化HTML元素(例如,点击操作用,列表用等),必要时使用WAI-ARIA属性增强。
<button><ul>/<li>确保支持键盘导航和焦点管理(例如,菜单中的方向键导航,模态框中的焦点捕获)。每个组件都应默认遵循无障碍标准和指南。
这意味着要提供正确的ARIA角色/状态,并使用屏幕阅读器测试。无障碍不是可选的——它是每个组件的基线功能。
Customizability and Theming
可定制性与主题化
A component should be easy to restyle or adapt to different design requirements. Avoid hard-coding visual styles that cannot be overridden.
Provide mechanisms for theming and styling, such as CSS variables, clearly documented class names, or style props. Ideally, components come with sensible default styling but allow developers to customize appearance with minimal effort (for example, by passing a className or using design tokens).
This principle ensures components can fit into any brand or design system without “fighting” against default styles.
组件应易于重新样式或适配不同的设计需求。避免硬编码无法覆盖的视觉样式。
提供主题化和样式定制机制,如CSS变量、文档清晰的类名或样式props。理想情况下,组件带有合理的默认样式,但允许开发者以最小的 effort 自定义外观(例如,通过传递className或使用设计令牌)。
这一原则确保组件无需“对抗”默认样式即可适配任何品牌或设计系统。
Lightweight and Performant
轻量与高性能
Components should be as lean as possible in terms of assets and dependencies. Avoid bloating a component with large library dependencies or overly complex logic, especially if that logic isn’t always needed.
Strive for good performance (both rendering and interaction) by minimizing unnecessary re-renders and using efficient algorithms for heavy tasks. If a component is data-intensive (like a large list or table), consider patterns like virtualization or incremental rendering, but keep such features optional.
Lightweight components are easier to maintain and faster for end users.
组件在资源和依赖方面应尽可能精简。避免用大型库依赖或过于复杂的逻辑使组件膨胀,尤其是当该逻辑并非始终需要时。
通过减少不必要的重渲染和对繁重任务使用高效算法,努力实现良好的性能(渲染和交互)。如果组件是数据密集型的(如大型列表或表格),可以考虑虚拟化或增量渲染等模式,但需将此类功能设为可选。
轻量组件更易于维护,对终端用户来说速度更快。
Transparency and Code Ownership
透明性与代码所有权
In open-source, consumers often benefit from having full visibility and control of component code. This spec encourages an “open-source first” mindset: components should not be black boxes.
When developers import or copy your component, they should be able to inspect how it works and modify it if needed. This principle underlies the emerging “copy-and-paste” distribution model (discussed later) where developers integrate component code directly into their projects.
By giving users ownership of the code, you increase trust and allow deeper customization.
Even if you distribute via a package, embrace transparency by providing source maps, readable code, and thorough documentation.
在开源领域,消费者通常受益于对组件代码的完全可见性和控制权。本规范鼓励“开源优先”的思维:组件不应是黑盒。
当开发者导入或复制你的组件时,他们应该能够检查其工作原理并在需要时进行修改。这一原则是新兴的“复制粘贴”分发模型(稍后讨论)的基础,开发者将组件代码直接集成到他们的项目中。
通过赋予用户代码所有权,你可以增加信任并允许更深层次的定制。
即使你通过包分发,也应通过提供源映射、可读代码和详尽文档来拥抱透明性。
State
状态管理
URL: /state
title: State
description: How to manage state in a component, as well as merging controllable and uncontrolled state.
Building flexible components that work in both controlled and uncontrolled modes is a hallmark of professional components.
URL: /state
title: 状态管理
description: 如何管理组件状态,以及合并受控和非受控状态。
构建同时支持受控和非受控模式的灵活组件是专业组件的标志。
Uncontrolled State
非受控状态
Uncontrolled state is when the component manages its own state internally. This is the default usage pattern for most components.
For example, here's a simple component that manages its own state internally:
Steppertsx
import { useState } from 'react';
export const Stepper = () => {
const [value, setValue] = useState(0);
return (
<div>
<p>{value}</p>
<button onClick={() => setValue(value + 1)}>Increment</button>
</div>
);
};非受控状态是指组件内部管理自身状态。这是大多数组件的默认使用模式。
例如,这是一个简单的组件,它内部管理自己的状态:
Steppertsx
import { useState } from 'react';
export const Stepper = () => {
const [value, setValue] = useState(0);
return (
<div>
<p>{value}</p>
<button onClick={() => setValue(value + 1)}>增加</button>
</div>
);
};Controlled State
受控状态
Controlled state is when the component's state is managed by the parent component. Rather than keeping track of the state internally, we delegate this responsibility to the parent component.
Let's rework the component to be controlled by the parent component:
Steppertsx
type StepperProps = {
value: number;
setValue: (value: number) => void;
};
export const Stepper = ({ value, setValue }: StepperProps) => (
<div>
<p>{value}</p>
<button onClick={() => setValue(value + 1)}>Increment</button>
</div>
);受控状态是指组件的状态由父组件管理。我们不内部跟踪状态,而是将此职责委托给父组件。
让我们重新设计组件,使其由父组件控制:
Steppertsx
type StepperProps = {
value: number;
setValue: (value: number) => void;
};
export const Stepper = ({ value, setValue }: StepperProps) => (
<div>
<p>{value}</p>
<button onClick={() => setValue(value + 1)}>增加</button>
</div>
);Merging states
合并状态
The best components support both controlled and uncontrolled state. This allows the component to be used in a variety of scenarios, and to be easily customized.
Radix UI maintain an internal utility for merging controllable and uncontrolled state called . While not intended for public use, registries like Kibo UI have implemented this utility to build their own Radix-like components.
use-controllable-stateLet's install the hook:
package
npm install @radix-ui/react-use-controllable-stateThis lightweight hook gives you the same state management patterns used internally by Radix UI's component library, ensuring your components behave consistently with industry standards.
The hook accepts three main parameters and returns a tuple with the current value and setter. Let's use it to merge the controlled and uncontrolled state of the component:
Steppertsx
import { useControllableState } from '@radix-ui/react-use-controllable-state';
type StepperProps = {
value: number;
defaultValue: number;
onValueChange: (value: number) => void;
};
export const Stepper = ({ value: controlledValue, defaultValue, onValueChange }: StepperProps) => {
const [value, setValue] = useControllableState({
prop: controlledValue, // The controlled value prop
defaultProp: defaultValue, // Default value for uncontrolled mode
onChange: onValueChange, // Called when value changes
});
return (
<div>
<p>{value}</p>
<button onClick={() => setValue(value + 1)}>Increment</button>
</div>
);
};最好的组件同时支持受控和非受控状态。这允许组件在各种场景中使用,并易于定制。
Radix UI维护了一个内部工具,用于合并受控和非受控状态,名为。虽然不打算公开使用,但像Kibo UI这样的注册表已经实现了这个工具来构建自己的类Radix组件。
use-controllable-state让我们安装这个hook:
package
npm install @radix-ui/react-use-controllable-state这个轻量hook提供了Radix UI组件库内部使用的相同状态管理模式,确保你的组件行为符合行业标准。
该hook接受三个主要参数,并返回包含当前值和设置器的元组。让我们用它来合并组件的受控和非受控状态:
Steppertsx
import { useControllableState } from '@radix-ui/react-use-controllable-state';
type StepperProps = {
value: number;
defaultValue: number;
onValueChange: (value: number) => void;
};
export const Stepper = ({ value: controlledValue, defaultValue, onValueChange }: StepperProps) => {
const [value, setValue] = useControllableState({
prop: controlledValue, // 受控值属性
defaultProp: defaultValue, // 非受控模式的默认值
onChange: onValueChange, // 值变化时调用
});
return (
<div>
<p>{value}</p>
<button onClick={() => setValue(value + 1)}>增加</button>
</div>
);
};Types
类型定义
URL: /types
title: Types
description: Extending the browser's native HTML elements for maximum customization.
When building reusable components, proper typing is essential for creating flexible, customizable, and type-safe interfaces. By following established patterns for component types, you can ensure your components are both powerful and easy to use.
URL: /types
title: 类型定义
description: 扩展浏览器原生HTML元素以实现最大程度的定制。
构建可重用组件时,正确的类型定义对于创建灵活、可定制且类型安全的接口至关重要。通过遵循组件类型的既定模式,你可以确保组件既强大又易于使用。
Single Element Wrapping
单元素包装
Each exported component should ideally wrap a single HTML or JSX element. This principle is fundamental to creating composable, customizable components.
When a component wraps multiple elements, it becomes difficult to customize specific parts without prop drilling or complex APIs. Consider this anti-pattern:
tsx
const Card = ({ title, description, footer, ...props }) => (
<div {...props}>
<div className="card-header">
<h2>{title}</h2>
<p>{description}</p>
</div>
<div className="card-footer">{footer}</div>
</div>
);As we discussed in Composition, this approach creates several problems:
- You can't customize the header styling without adding more props
- You can't control the HTML elements used for title and description
- You're forced into a specific DOM structure
Instead, each layer should be its own component. This allows you to customize each layer independently, and to control the exact HTML elements used for the title and description.
The benefits of this approach are:
- Maximum customization - Users can style and modify each layer independently
- No prop drilling - Props go directly to the element that needs them
- Semantic HTML - Users can see and control the exact DOM structure
- Better accessibility - Direct control over ARIA attributes and semantic elements
- Simpler mental model - One component = one element
每个导出的组件理想情况下应包装单个HTML或JSX元素。这一原则是创建可组合、可定制组件的基础。
当组件包装多个元素时,无需属性透传或复杂API即可自定义特定部分变得困难。考虑这种反模式:
tsx
const Card = ({ title, description, footer, ...props }) => (
<div {...props}>
<div className="card-header">
<h2>{title}</h2>
<p>{description}</p>
</div>
<div className="card-footer">{footer}</div>
</div>
);正如我们在组件组合中讨论的,这种方法存在几个问题:
- 不添加更多props就无法自定义标题样式
- 无法控制标题和描述使用的HTML元素
- 被迫使用特定的DOM结构
相反,每个层级都应是独立的组件。这允许你独立自定义每个层级,并控制标题和描述使用的确切HTML元素。
这种方法的好处是:
- 最大程度的定制 - 用户可以独立样式和修改每个层级
- 无属性透传 - props直接传递给需要它们的元素
- 语义化HTML - 用户可以查看和控制确切的DOM结构
- 更好的无障碍特性 - 直接控制ARIA属性和语义化元素
- 更简单的心智模型 - 一个组件 = 一个元素
Extending HTML Attributes
扩展HTML属性
Every component should extend the native HTML attributes of the element it wraps. This ensures users have full control over the underlying HTML element.
每个组件都应扩展它所包装的元素的原生HTML属性。这确保用户对底层HTML元素有完全的控制权。
Basic Pattern
基本模式
tsx
export type CardRootProps = React.ComponentProps<'div'> & {
// Add your custom props here
variant?: 'default' | 'outlined';
};
export const CardRoot = ({ variant = 'default', ...props }: CardRootProps) => <div {...props} />;tsx
export type CardRootProps = React.ComponentProps<'div'> & {
// 在此添加自定义props
variant?: 'default' | 'outlined';
};
export const CardRoot = ({ variant = 'default', ...props }: CardRootProps) => <div {...props} />;Common HTML Attribute Types
常见HTML属性类型
React provides type definitions for all HTML elements. Use the appropriate one for your component:
tsx
// For div elements
type DivProps = React.ComponentProps<'div'>;
// For button elements
type ButtonProps = React.ComponentProps<'button'>;
// For input elements
type InputProps = React.ComponentProps<'input'>;
// For form elements
type FormProps = React.ComponentProps<'form'>;
// For anchor elements
type LinkProps = React.ComponentProps<'a'>;React为所有HTML元素提供了类型定义。为你的组件使用合适的类型:
tsx
// 针对div元素
type DivProps = React.ComponentProps<'div'>;
// 针对button元素
type ButtonProps = React.ComponentProps<'button'>;
// 针对input元素
type InputProps = React.ComponentProps<'input'>;
// 针对form元素
type FormProps = React.ComponentProps<'form'>;
// 针对锚点元素
type LinkProps = React.ComponentProps<'a'>;Handling Different Element Types
处理不同元素类型
When a component can render as different elements, use generics or union types:
tsx
// Using discriminated unions
export type ButtonProps =
| (React.ComponentProps<'button'> & { asChild?: false })
| (React.ComponentProps<'div'> & { asChild: true });
// Or with a polymorphic approach
export type PolymorphicProps<T extends React.ElementType> = {
as?: T;
} & React.ComponentPropsWithoutRef<T>;当组件可以渲染为不同元素时,使用泛型或联合类型:
tsx
// 使用区分联合类型
export type ButtonProps =
| (React.ComponentProps<'button'> & { asChild?: false })
| (React.ComponentProps<'div'> & { asChild: true });
// 或使用多态方法
export type PolymorphicProps<T extends React.ElementType> = {
as?: T;
} & React.ComponentPropsWithoutRef<T>;Extending custom components
扩展自定义组件
If you're extending an existing component, you can use the type to get the props of the component.
ComponentPropstsx
import type { ComponentProps } from 'react';
export type ShareButtonProps = ComponentProps<'button'>;
export const ShareButton = (props: ShareButtonProps) => <button {...props} />;如果你正在扩展现有组件,可以使用类型获取组件的props。
ComponentPropstsx
import type { ComponentProps } from 'react';
export type ShareButtonProps = ComponentProps<'button'>;
export const ShareButton = (props: ShareButtonProps) => <button {...props} />;Exporting Types
导出类型
Always export your component prop types. This makes them accessible to consumers for various use cases.
Exporting types enables several important patterns:
tsx
// 1. Extracting specific prop types
import type { CardRootProps } from '@/components/ui/card';
const variant = CardRootProps['variant'];
// 2. Extending components
export type ExtendedCardProps = CardRootProps & {
isLoading?: boolean;
};
// 3. Creating wrapper components
const MyCard = (props: CardRootProps) => (
<CardRoot {...props} className={cn('my-custom-class', props.className)} />
);
// 4. Type-safe prop forwarding
function useCardProps(): Partial<CardRootProps> {
return {
variant: 'outlined',
className: 'custom-card',
};
}Your exported types should be named . This is a convention that helps other developers understand the purpose of the type.
<ComponentName>Props始终导出组件的props类型。这使得它们可被消费者用于各种场景。
导出类型支持几种重要模式:
tsx
// 1. 提取特定prop类型
import type { CardRootProps } from '@/components/ui/card';
const variant = CardRootProps['variant'];
// 2. 扩展组件
export type ExtendedCardProps = CardRootProps & {
isLoading?: boolean;
};
// 3. 创建包装组件
const MyCard = (props: CardRootProps) => (
<CardRoot {...props} className={cn('my-custom-class', props.className)} />
);
// 4. 类型安全的属性转发
function useCardProps(): Partial<CardRootProps> {
return {
variant: 'outlined',
className: 'custom-card',
};
}你的导出类型应命名为。这是一个约定,有助于其他开发者理解类型的用途。
<ComponentName>PropsBest Practices
最佳实践
1. Always Spread Props Last
1. 始终最后展开Props
Ensure users can override any default props:
tsx
// ✅ Good - user props override defaults
<div className="default-class" {...props} />
// ❌ Bad - defaults override user props
<div {...props} className="default-class" />确保用户可以覆盖任何默认props:
tsx
// ✅ 良好 - 用户props覆盖默认值
<div className="default-class" {...props} />
// ❌ 糟糕 - 默认值覆盖用户props
<div {...props} className="default-class" />2. Avoid Prop Name Conflicts
2. 避免Prop名称冲突
Don't use prop names that conflict with HTML attributes unless intentionally overriding:
tsx
// ❌ Bad - conflicts with HTML title attribute
export type CardProps = React.ComponentProps<'div'> & {
title: string; // This conflicts with the HTML title attribute
};
// ✅ Good - use a different name
export type CardProps = React.ComponentProps<'div'> & {
heading: string;
};除非有意覆盖,否则不要使用与HTML属性冲突的prop名称:
tsx
// ❌ 糟糕 - 与HTML title属性冲突
export type CardProps = React.ComponentProps<'div'> & {
title: string; // 与HTML title属性冲突
};
// ✅ 良好 - 使用不同的名称
export type CardProps = React.ComponentProps<'div'> & {
heading: string;
};3. Document Custom Props
3. 文档化自定义Props
Add JSDoc comments to custom props for better developer experience:
tsx
export type DialogProps = React.ComponentProps<'div'> & {
/** Whether the dialog is currently open */
open: boolean;
/** Callback when the dialog requests to be closed */
onOpenChange: (open: boolean) => void;
/** Whether to render the dialog in a portal */
modal?: boolean;
};为自定义props添加JSDoc注释,提升开发者体验:
tsx
export type DialogProps = React.ComponentProps<'div'> & {
/** 对话框当前是否打开 */
open: boolean;
/** 对话框请求关闭时的回调 */
onOpenChange: (open: boolean) => void;
/** 是否在门户中渲染对话框 */
modal?: boolean;
};