react-spectrum-s2
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Spectrum S2 (Spectrum 2)
React Spectrum S2 (Spectrum 2)
If the requirements do not clearly specify which React Spectrum component to use, consult the Component Decision Tree before choosing a component.
If the request involves a Figma design, frame, or URL — or if the Figma MCP (,, etc.) is available — consult Implementing Figma designs with React Spectrum S2 before generating code.
get_design_contextsearch_design_systemWhen writing tests that exercise S2 components, consult Testing with React Spectrum S2 and prefer the ARIA pattern testers from over hand-rolled role/selector queries.
@react-spectrum/test-utils如果需求未明确指定使用哪个 React Spectrum 组件,请先查阅组件决策树再选择组件。
如果需求涉及 Figma 设计、画框或 URL——或者可用 Figma MCP(、 等)——请先查阅使用 React Spectrum S2 实现 Figma 设计再生成代码。
get_design_contextsearch_design_system编写测试 S2 组件的用例时,请查阅使用 React Spectrum S2 进行测试,优先使用 中的 ARIA 模式测试器,而非手动编写的 role/选择器查询。
@react-spectrum/test-utilsReact Spectrum S2 vs React Aria Components
React Spectrum S2 与 React Aria Components
React Spectrum S2 is built on top of React Aria Components. The S2 components add Spectrum 2 styling, behavior, and slot structure on top of the unstyled React Aria primitives. Always prefer S2 components for React Spectrum work because they are pre-styled, design-system compliant, and cover most common UI patterns.
Only reach for React Aria Components directly when:
- Building a custom component because no S2 component matches the requirements. Follow Creating Custom Components and pair the React Aria primitive with the S2 macro for Spectrum styling.
style - You need a utility such as ,
FocusScope,VisuallyHidden,useFocusRing, etc.mergeProps
The React Aria Components documentation is bundled under . Many unstyled React Aria Components share the same name as S2 components, so ensure that you're searching and accessing the correct docs where needed.
references/react-aria/React Spectrum S2 构建于 React Aria Components 之上。S2 组件在无样式的 React Aria 原语基础上,添加了 Spectrum 2 的样式、行为和插槽结构。进行 React Spectrum 开发时,请始终优先使用 S2 组件,因为它们已预设样式、符合设计系统规范,且覆盖了绝大多数常见 UI 模式。
仅在以下情况直接使用 React Aria Components:
- 没有符合需求的 S2 组件,需要构建自定义组件。请遵循创建自定义组件,将 React Aria 原语与 S2 的 宏结合使用以实现 Spectrum 样式。
style - 需要 、
FocusScope、VisuallyHidden、useFocusRing等工具。mergeProps
React Aria Components 文档打包在 目录下。许多无样式的 React Aria Components 与 S2 组件同名,因此请确保在需要时搜索并访问正确的文档。
references/react-aria/Imports
导入
Use subpath imports matching the documentation — don't import from the package barrel :
'@react-spectrum/s2'tsx
import {Button} from '@react-spectrum/s2/Button';
import {Card, CardPreview, Image, Content, Text, Footer} from '@react-spectrum/s2/Card';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import Folder from '@react-spectrum/s2/icons/Folder';
import CloudUpload from '@react-spectrum/s2/illustrations/gradient/generic2/CloudUpload';Common types and list-data hooks are re-exported from — prefer importing from there instead of , , or :
@react-spectrum/s2react-aria-componentsreact-stately@react-types/*tsx
import type {Key, Selection, SortDescriptor, PressEvent, RangeValue, DateValue, DateRange, TimeValue, RouterConfig} from '@react-spectrum/s2';
import {useListData, useTreeData, useAsyncList} from '@react-spectrum/s2';请使用与文档一致的子路径导入——不要从包的统一入口 导入:
'@react-spectrum/s2'tsx
import {Button} from '@react-spectrum/s2/Button';
import {Card, CardPreview, Image, Content, Text, Footer} from '@react-spectrum/s2/Card';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import Folder from '@react-spectrum/s2/icons/Folder';
import CloudUpload from '@react-spectrum/s2/illustrations/gradient/generic2/CloudUpload';常用类型和列表数据 Hook 已从 重新导出——优先从该包导入,而非从 、 或 导入:
@react-spectrum/s2react-aria-componentsreact-stately@react-types/*tsx
import type {Key, Selection, SortDescriptor, PressEvent, RangeValue, DateValue, DateRange, TimeValue, RouterConfig} from '@react-spectrum/s2';
import {useListData, useTreeData, useAsyncList} from '@react-spectrum/s2';Use the typed Item for each collection
每个集合使用对应的类型化 Item 组件
Each S2 collection component has its own item export — there is no generic component. → /; → /; → /; → ; → (with ); → ////; → ; → ; → ; → (with //).
ItemMenuMenuItemMenuSectionPickerPickerItemPickerSectionComboBoxComboBoxItemComboBoxSectionListViewListViewItemTreeViewTreeViewItemTreeViewItemContentTableViewRowColumnCellTableHeaderTableBodySegmentedControlSegmentedControlItemTagGroupTagBreadcrumbsBreadcrumbAccordionAccordionItemAccordionItemHeaderAccordionItemTitleAccordionItemPanel每个 S2 集合组件都有自己的 item 导出——不存在通用的 组件。对应关系如下: → /; → /; → /; → ; → (搭配 ); → ////; → ; → ; → ; → (搭配 //)。
ItemMenuMenuItemMenuSectionPickerPickerItemPickerSectionComboBoxComboBoxItemComboBoxSectionListViewListViewItemTreeViewTreeViewItemTreeViewItemContentTableViewRowColumnCellTableHeaderTableBodySegmentedControlSegmentedControlItemTagGroupTagBreadcrumbsBreadcrumbAccordionAccordionItemAccordionItemHeaderAccordionItemTitleAccordionItemPanelStyling
样式
Use S2 components and the S2 macro as the default styling approach.
style- Prefer S2 components first; use their prop only for layout-style properties.
styles - For generic layouts (flex, grid, etc.), use native HTML elements with the macro.
style - Avoid using Tailwind, ,
radix-ui, or any other third-party design system in S2 implementations.shadcn/ui - IMPORTANT: avoid using and
UNSAFE_style.UNSAFE_className
S2 components take a prop (plural) restricted to layout properties:
styles- ,
margin,marginStart,marginEnd,marginTop,marginBottom,marginXmarginY - ,
width,minWidthmaxWidth - ,
flexGrow,flexShrinkflexBasis - ,
justifySelf,alignSelforder - ,
gridArea,gridRow,gridRowStart,gridRowEnd,gridColumn,gridColumnStartgridColumnEnd - ,
position,zIndex,top,bottom,inset,insetX,insetY,insetStartinsetEnd visibility- ,
height,minHeight(only in specific components without an intrinsic height)maxHeight
tsx
<Button styles={style({marginStart: 8})}>Edit</Button>Native HTML elements and React Aria Components take — there the macro is unrestricted:
className={style(...)}tsx
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import {Checkbox} from 'react-aria-components';
<div className={style({display: 'grid', gap: 12, padding: 16, backgroundColor: 'gray-75'})}>
<h2 className={style({font: 'heading-sm'})}>Preferences</h2>
<Checkbox
className={style({
display: 'flex',
alignItems: 'center',
gap: 8,
color: {
default: 'neutral',
isSelected: 'blue-900'
}
})}
/>
</div>Spacing values follow a 4px grid (, , , , , , …).
02481216默认使用 S2 组件和 S2 的 宏作为样式方案。
style- 优先使用 S2 组件;仅在需要布局类属性时使用其 属性。
styles - 对于通用布局(flex、grid 等),使用原生 HTML 元素搭配 宏。
style - 在 S2 实现中避免使用 Tailwind、、
radix-ui或任何其他第三方设计系统。shadcn/ui - 重要提示:避免使用 和
UNSAFE_style。UNSAFE_className
S2 组件接受一个 属性(注意是复数形式),仅支持布局相关属性:
styles- ,
margin,marginStart,marginEnd,marginTop,marginBottom,marginXmarginY - ,
width,minWidthmaxWidth - ,
flexGrow,flexShrinkflexBasis - ,
justifySelf,alignSelforder - ,
gridArea,gridRow,gridRowStart,gridRowEnd,gridColumn,gridColumnStartgridColumnEnd - ,
position,zIndex,top,bottom,inset,insetX,insetY,insetStartinsetEnd visibility- ,
height,minHeight(仅在无固有高度的特定组件中可用)maxHeight
tsx
<Button styles={style({marginStart: 8})}>Edit</Button>原生 HTML 元素和 React Aria Components 使用 ——这种情况下宏没有使用限制:
className={style(...)}tsx
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import {Checkbox} from 'react-aria-components';
<div className={style({display: 'grid', gap: 12, padding: 16, backgroundColor: 'gray-75'})}>
<h2 className={style({font: 'heading-sm'})}>Preferences</h2>
<Checkbox
className={style({
display: 'flex',
alignItems: 'center',
gap: 8,
color: {
default: 'neutral',
isSelected: 'blue-900'
}
})}
/>
</div>间距值遵循 4px 网格体系(、、、、、……)。
02481216One style({...})
call per element
style({...})每个元素仅调用一次 style({...})
style({...})Express runtime variation inside a single call using its conditional system, not by producing multiple style strings and combining them on the element:
style({...})- Conditional keys can be nested objects () or boolean conditions starting with
variant: {primary: ..., secondary: ...}/is(allows,isSelected,allowsRemoving). The macro returns a function — call it with the runtime values:isHovered.styles({variant, isSelected}) - Don't concatenate macro results via template literals, ,
clsx, or string spaces — the class names encode style precedence and concatenation breaks it. Useclassnamesif you genuinely need to merge two separate style strings at runtime.mergeStyles - Don't combine (or
className={style({...})}) with an inlinestyleson the same element. The inline prop bypasses tokens and breaks layer ordering. Use the inlinestyle={{...}}prop only for values that genuinely can't be known at build time (e.g. a drag-handler position).style
tsx
// ❌ Two near-identical style calls combined at the call site.
const base = style({padding: 8, backgroundColor: 'gray-100'});
const active = style({backgroundColor: 'accent'});
<div className={`${base} ${isActive ? active : ''}`} />
// ✅ One call with a runtime condition.
const card = style({
padding: 8,
backgroundColor: {
default: 'gray-100',
isActive: 'accent'
}
});
<div className={card({isActive})} />If a value seems impossible to express in the macro, check the Style Macro reference before falling back to inline styles — most CSS properties (grid placement, overflow, position, sizing, display) are supported.
请在单个 调用内部使用其条件系统来表达运行时变化,而非生成多个样式字符串再在元素上组合:
style({...})- 条件键可以是嵌套对象(),也可以是
variant: {primary: ..., secondary: ...}/is开头的布尔条件(allows、isSelected、allowsRemoving)。宏会返回一个函数——用运行时值调用它:isHovered。styles({variant, isSelected}) - 不要通过模板字面量、、
clsx或字符串拼接来组合宏的结果——类名中编码了样式优先级,拼接会破坏这一机制。如果确实需要在运行时合并两个独立的样式字符串,请使用classnames。mergeStyles - 不要在同一元素上同时使用 (或
className={style({...})})和内联styles。内联属性会绕过 token 系统并破坏层级顺序。仅当值确实无法在构建时确定时(例如拖拽处理器的位置),才使用内联style={{...}}属性。style
tsx
// ❌ Two near-identical style calls combined at the call site.
const base = style({padding: 8, backgroundColor: 'gray-100'});
const active = style({backgroundColor: 'accent'});
<div className={`${base} ${isActive ? active : ''}`} />
// ✅ One call with a runtime condition.
const card = style({
padding: 8,
backgroundColor: {
default: 'gray-100',
isActive: 'accent'
}
});
<div className={card({isActive})} />如果某个值似乎无法用宏表达,请先查阅样式宏参考文档,再回退到内联样式——绝大多数 CSS 属性(网格定位、overflow、position、尺寸、display)都支持。
Style macro values are tokens, not raw CSS
样式宏的值是 token,而非原生 CSS
The macro is a typed token system. Raw CSS strings fail type-checking and push the agent toward inline-style workarounds. Common confusions:
- /
width/height/maxWidth/maxHeight/minWidth:minHeight(not'full'),'100%', or a number.'screen' - /
alignItems/justifyContent/alignSelf:justifySelf/'start'/'center'/'end'(not'space-between'/'flex-start').'flex-end' - Spacing (,
margin,padding,gap, etc.): numeric pixels on the 4px grid, not strings liketop.'1rem' - :
borderRadius,'none','sm','default','lg','xl','full'. There is no'pill'.'md' - Colors: token names like ,
'gray-100','accent','neutral'— not hex,'neutral-subdued', orrgb().var(--…) - Fonts: ,
font: 'heading-xl', etc. — not CSS font shorthand.font: 'body-sm' - Use logical sides — /
paddingStart/paddingEnd/marginStart/marginEnd/insetStart/insetEnd— not physicalborderStartStartRadius/paddingLeft/paddingRight/marginLeft/marginRight. The logical variants flip correctly under RTL.borderTopLeftRadius
Prefer semantic color tokens when the color carries meaning: , , , , , for errors/success/info/warning/brand — not ///. Reach for raw hue tokens only for decorative or chart colors.
'accent''neutral''negative''positive''informative''notice''red-…''green-…''blue-…''orange-…'该宏是一个类型化的 token 系统。原生 CSS 字符串无法通过类型检查,会导致 Agent 不得不使用内联样式作为变通方案。常见误区:
- /
width/height/maxWidth/maxHeight/minWidth:取值为minHeight(而非'full')、'100%'或数字。'screen' - /
alignItems/justifyContent/alignSelf:取值为justifySelf/'start'/'center'/'end'(而非'space-between'/'flex-start')。'flex-end' - 间距(、
margin、padding、gap等):4px 网格体系下的数字像素值,而非top之类的字符串。'1rem' - :取值为
borderRadius、'none'、'sm'、'default'、'lg'、'xl'、'full',没有'pill'。'md' - 颜色:使用 token 名称,如 、
'gray-100'、'accent'、'neutral'——而非十六进制值、'neutral-subdued'或rgb()。var(--…) - 字体:使用 、
font: 'heading-xl'等形式——而非 CSS 字体简写。font: 'body-sm' - 使用逻辑边距——/
paddingStart/paddingEnd/marginStart/marginEnd/insetStart/insetEnd——而非物理边距borderStartStartRadius/paddingLeft/paddingRight/marginLeft/marginRight。逻辑属性在 RTL(从右到左)布局下会正确翻转。borderTopLeftRadius
当颜色具有语义含义时,优先使用语义化颜色 token:错误/成功/信息/警告/品牌色分别使用 、、、、、——而非 ///。仅在装饰性或图表颜色中使用原始色相 token。
'accent''neutral''negative''positive''informative''notice''red-…''green-…''blue-…''orange-…'Don't restate default prop values
不要重复设置默认属性值
variant="primary"Buttonsize="M"density="regular"Buttonvariant="primary"size="M"density="regular"Responsive design
响应式设计
Layouts should adapt to viewport size. The macro provides built-in breakpoint conditions on layout properties — use them rather than producing a fixed-width design.
styleBreakpoints (min-width, mobile-first): 480, 640, 768, 1024, 1280, 1536. The value applies on the smallest viewport; each breakpoint takes over above its threshold.
xssmmdlgxl2xldefaulttsx
const grid = style({
display: 'grid',
gridTemplateColumns: {
default: '1fr',
sm: 'repeat(2, 1fr)',
lg: 'repeat(3, 1fr)',
xl: 'repeat(4, 1fr)'
},
gap: {default: 12, md: 16, lg: 24},
padding: {default: 16, md: 24, lg: 32}
});布局应适配视口大小。 宏在布局属性上提供了内置的断点条件——请使用这些条件,而非设计固定宽度的布局。
style断点(最小宽度,移动优先): 480、 640、 768、 1024、 1280、 1536。 值适用于最小视口;每个断点在超过其阈值后生效。
xssmmdlgxl2xldefaulttsx
const grid = style({
display: 'grid',
gridTemplateColumns: {
default: '1fr',
sm: 'repeat(2, 1fr)',
lg: 'repeat(3, 1fr)',
xl: 'repeat(4, 1fr)'
},
gap: {default: 12, md: 16, lg: 24},
padding: {default: 16, md: 24, lg: 32}
});Component composition
组件组合
S2 components define their own internal DOM and slot structure. Don't inject wrapper elements where the component expects a specific child API.
- Don't wrap collection-item children in a ,
<div>,<span>, etc. Use the slot components the component documents:<section>- rows:
TreeView, not aTreeViewItemContent.div - ,
ListView,Menu,Picker,ComboBox, etc.:Tabs(withText/slot="label"where relevant) plus documented icon/image/avatar slots.slot="description" - ,
Card,AssetCard,UserCard: prescribed slot components (ProductCard,CardPreview,Image,Content,Text, etc.).Footer
- Extra layout inside a slot goes inside the slot component, not around it.
- Before composing a component, read the API section of its docs page — it lists expected child components, slots, and constraints. The docs examples are the source of truth.
S2 组件定义了自己的内部 DOM 和插槽结构。在组件期望特定子元素 API 的位置,不要插入包装元素。
- 不要将集合项的子元素包裹在 、
<div>、<span>等标签中。请使用组件文档中规定的插槽组件:<section>- 行:使用
TreeView,而非TreeViewItemContent。div - 、
ListView、Menu、Picker、ComboBox等:使用Tabs(在相关位置搭配Text/slot="label")以及文档中规定的 icon/image/avatar 插槽。slot="description" - 、
Card、AssetCard、UserCard:使用指定的插槽组件(ProductCard、CardPreview、Image、Content、Text等)。Footer
- 插槽内部的额外布局应放在插槽组件内部,而非包裹在插槽组件外面。
- 在组合组件之前,请阅读其文档页面的 API 部分——其中列出了支持的子组件、插槽和约束条件。文档示例是唯一可信来源。
Buttons with text and icon
带文本和图标的按钮
ButtonActionButtonLinkButton<Text>aria-labelTexttsx
import {ActionButton, Text} from '@react-spectrum/s2/ActionButton';
import Download from '@react-spectrum/s2/icons/Download';
<ActionButton>
<Download />
<Text>Download</Text>
</ActionButton>同时包含图标和文本标签的 // 需要将标签包裹在 中——图标旁边的纯字符串子元素会渲染错误。(仅含图标的子元素可以正常渲染,但需要设置 。) 会从每个按钮自身的子路径中重新导出:
ButtonActionButtonLinkButton<Text>aria-labelTexttsx
import {ActionButton, Text} from '@react-spectrum/s2/ActionButton';
import Download from '@react-spectrum/s2/icons/Download';
<ActionButton>
<Download />
<Text>Download</Text>
</ActionButton>Exceptions: components that accept arbitrary content
例外:接受任意内容的组件
A few components are explicitly free-form: ; Dialog/Popover bodies; / panels. Compose these with native elements + the macro like any page section. For any other component, defer to slot components.
TableViewCellDisclosureAccordionstyle少数组件明确支持自由形式的内容: 的 ;Dialog/Popover 的主体;/ 的面板。可以像处理页面区块一样,用原生元素 + 宏来组合这些组件的内容。对于其他任何组件,请遵循插槽组件的使用规范。
TableViewCellDisclosureAccordionstyleOnly use slot
values the component documents
slot仅使用组件文档中规定的 slot
值
slotOnly pass when the parent's docs name that slot.
slot- Don't put on a
slot="close"inside aButtonDialog— the dialog handles its own close;ButtonGroupon something else causes the parent context to hide it.slot="close" - Don't sprinkle /
slot="label"onslot="description"outside a component that exposes those slots.Text
仅当父组件的文档中明确列出了该插槽时,才传递 属性。
slot- 不要在 的
Dialog内的ButtonGroup上设置Button——对话框会自行处理关闭逻辑;在其他元素上设置slot="close"会导致父上下文将其隐藏。slot="close" - 不要在未暴露对应插槽的组件外部的 上随意添加
Text/slot="label"。slot="description"
Don't reinvent Card
/ CardView
CardCardView不要自行实现 Card
/ CardView
CardCardViewFor grids of objects/files/products/people, use plus a prescribed variant (, , ) or composed with ///. Don't emit hand-rolled card divs or wrappers. Build a custom card with the documented slot components only when no variant fits.
CardViewAssetCardUserCardProductCardCardCardPreviewContentTextFooter<article>对于对象/文件/产品/人物的网格布局,请使用 搭配指定的变体(、、),或使用由 /// 组合而成的 。不要手动编写卡片的 div 或 包装器。仅当没有合适的变体时,才使用文档中规定的插槽组件构建自定义卡片。
CardViewAssetCardUserCardProductCardCardPreviewContentTextFooterCard<article>Collection components handle their own scrolling
集合组件自行处理滚动
TableViewListViewTreeViewCardViewMenuListBoxoverflowoverflowYoverflowXheightmaxHeightstylestsx
// ❌ Nested scroller around CardView.
<div className={style({overflowY: 'auto', height: 480})}><CardView>…</CardView></div>
// ✅ Let the collection size itself and scroll internally.
<CardView styles={style({height: 480})}>…</CardView>TableViewListViewTreeViewCardViewMenuListBoxoverflowoverflowYoverflowXstylesheightmaxHeighttsx
// ❌ Nested scroller around CardView.
<div className={style({overflowY: 'auto', height: 480})}><CardView>…</CardView></div>
// ✅ Let the collection size itself and scroll internally.
<CardView styles={style({height: 480})}>…</CardView>Collections
集合组件
Collection components (, , , , , , , , , , etc.) share a small set of conventions. Getting them wrong causes runtime warnings, broken selection, and broken keyboard navigation.
MenuPickerComboBoxListViewTreeViewTableViewCardViewSegmentedControlTagGroupBreadcrumbs集合组件(、、、、、、、、、 等)遵循一套通用的约定。违反这些约定会导致运行时警告、选择功能失效和键盘导航异常。
MenuPickerComboBoxListViewTreeViewTableViewCardViewSegmentedControlTagGroupBreadcrumbsEvery item needs an id
id每个项都需要 id
idItems use for selection, , sort, expansion, and React reconciliation. Static items get a literal ; dynamic items get inside the render function. When using , set both and React's :
idonAction(key)idid={item.something}.mapidkeytsx
// ✅ With array.map — set BOTH `id` (for the collection) and `key` (for React).
<ListView aria-label="Files">
{files.map(item => (
<ListViewItem key={item.id} id={item.id}>{item.name}</ListViewItem>
))}
</ListView>When passing data via the prop and using a render function, only is needed.
itemsid项的 用于选择、、排序、展开和 React 的协调过程。静态项使用字面量 ;动态项在渲染函数中设置 。使用 时,请同时设置 (供集合组件使用)和 React 的 :
idonAction(key)idid={item.something}.mapidkeytsx
// ✅ With array.map — set BOTH `id` (for the collection) and `key` (for React).
<ListView aria-label="Files">
{files.map(item => (
<ListViewItem key={item.id} id={item.id}>{item.name}</ListViewItem>
))}
</ListView>通过 属性传递数据并使用渲染函数时,仅需设置 。
itemsidtextValue
when item children aren't plain text
textValue当项的子元素不是纯文本时设置 textValue
textValueIf an item's children include anything other than a plain string (icon, avatar, multi-slot layout, custom component), set . The collection uses it for screen-reader announcements, typeahead, and drag-and-drop labels. Omitting it produces a runtime warning.
textValue="..."tsx
<ListViewItem id={item.id} textValue={item.name}>
<FileIcon />
<Text slot="label">{item.name}</Text>
<Text slot="description">{item.size}</Text>
</ListViewItem>如果项的子元素包含纯字符串之外的内容(图标、头像、多插槽布局、自定义组件),请设置 。集合组件会将其用于屏幕阅读器播报、预输入和拖拽标签。省略该属性会产生运行时警告。
textValue="..."tsx
<ListViewItem id={item.id} textValue={item.name}>
<FileIcon />
<Text slot="label">{item.name}</Text>
<Text slot="description">{item.size}</Text>
</ListViewItem>aria-label
on the collection container
aria-label集合容器上的 aria-label
aria-labelEvery collection (, , , , , , , , ) needs an accessible name — or .
ListViewTableViewCardViewTreeViewMenuListBoxGridListTagGroupBreadcrumbsaria-label="..."aria-labelledby="..."每个集合组件(、、、、、、、、)都需要一个可访问名称——即 或 。
ListViewTableViewCardViewTreeViewMenuListBoxGridListTagGroupBreadcrumbsaria-label="..."aria-labelledby="..."Empty and loading states are built in
内置空状态和加载状态
- Empty state: pass returning an
renderEmptyState. Don't conditionally swap the whole collection for a custom emptyIllustratedMessage.div - Async data: use (or the user's preferred data fetching library) plus the collection's
useAsyncList/loadingStateprops. Don't render a separate spinner.onLoadMore
- 空状态:传入返回 的
IllustratedMessage。不要有条件地将整个集合替换为自定义的空renderEmptyState。div - 异步数据:使用 (或用户偏好的数据请求库)搭配集合组件的
useAsyncList/loadingState属性。不要单独渲染加载指示器。onLoadMore
Bulk actions with ActionBar
使用 ActionBar 实现批量操作
Use the prop on a collection to show an when items are selected. The collection passes the current to the callback and wires up count and clear-selection automatically — don't pass or to manually.
renderActionBarActionBarselectedKeysselectedItemCountonClearSelectionActionBar在集合组件上使用 属性,以便在选中项时显示 。集合组件会将当前的 传递给回调函数,并自动关联计数和清除选择功能——不要手动向 传递 或 。
renderActionBarActionBarselectedKeysActionBarselectedItemCountonClearSelectionTypography
排版
TextHeadingContentstyletsx
// ✅ Standalone heading.
<h1 className={style({font: 'heading-xl'})}>Project overview</h1>
// ❌ Standalone <Heading> with no slot context.
<Heading className={style({font: 'heading-xl'})}>Project overview</Heading>The shorthand sets , , , , and a default . Don't add a redundant next to a value unless you actually want to override the default.
fontfontFamilyfontSizefontWeightlineHeightcolorcolorfontTextHeadingContentstyletsx
// ✅ Standalone heading.
<h1 className={style({font: 'heading-xl'})}>Project overview</h1>
// ❌ Standalone <Heading> with no slot context.
<Heading className={style({font: 'heading-xl'})}>Project overview</Heading>fontfontFamilyfontSizefontWeightlineHeightcolorfontcolorFont scales — pick by role, not size
字体缩放——按角色选择,而非大小
- — page/section headings (
heading-*–h1); tracks the document outline.h6 - — titles inside components (card titles, dialog titles, tile labels) that aren't part of the page outline.
title-* - — running prose and long-form copy.
body-* - — supporting metadata, captions, timestamps, footnotes.
detail-* - — interactive UI text: buttons, menu items, form labels, table cells.
ui-* - — inline or block code (monospace family).
code-*
Sizes are , , default (no suffix), , , , (not every family has every size — see the Style Macro reference).
-xs-sm-lg-xl-2xl-3xl- —— 页面/章节标题(
heading-*–h1);对应文档大纲结构。h6 - —— 组件内部的标题(卡片标题、对话框标题、磁贴标签),不属于页面大纲的一部分。
title-* - —— 正文段落和长文本内容。
body-* - —— 辅助元数据、说明文字、时间戳、脚注。
detail-* - —— 交互式 UI 文本:按钮、菜单项、表单标签、表格单元格。
ui-* - —— 行内或块级代码(等宽字体族)。
code-*
尺寸包括 、、默认(无后缀)、、、、(并非每个字体族都支持所有尺寸——参见样式宏参考文档)。
-xs-sm-lg-xl-2xl-3xlProvider scope
Provider 作用域
Mount a single at the application root (inside , around the top-level layout).
Providerbody- Don't wrap ,
<html>, or<head>with<body>.Provider - Don't add a around a Dialog, Popover, Toast, or other portaled overlay — they inherit through context.
Provider - Don't stack s. Nesting is only correct when scoping a different
Provider/locale/routerto a subtree, which is rare.colorScheme - Let the manage
Provider. Don't hard-codecolorSchemeorcolorScheme="light"to make a screenshot match — for one-off light/dark color differences, usecolorScheme="dark".lightDark()
If your app uses , place a single as a sibling of the root layout, inside .
ToastQueue<ToastContainer />Providertsx
import {Provider} from '@react-spectrum/s2/Provider';
import {ToastContainer} from '@react-spectrum/s2/Toast';
function App() {
return (
<Provider>
<ToastContainer />
<YourApp />
</Provider>
);
}在应用根节点挂载单个 (位于 内部,包裹顶层布局)。
Providerbody- 不要用 包裹
Provider、<html>或<head>。<body> - 不要在 Dialog、Popover、Toast 或其他 portal 弹层外面添加 ——它们会通过 context 继承样式。
Provider - 不要堆叠 。仅当需要为子树设置不同的
Provider/locale/router时才允许嵌套,这种情况很少见。colorScheme - 让 管理
Provider。不要为了让截图匹配而硬编码colorScheme或colorScheme="light"——对于一次性的亮/暗颜色差异,请使用colorScheme="dark"。lightDark()
如果你的应用使用 ,请将单个 放在 内部,作为根布局的同级元素。
ToastQueue<ToastContainer />Providertsx
import {Provider} from '@react-spectrum/s2/Provider';
import {ToastContainer} from '@react-spectrum/s2/Toast';
function App() {
return (
<Provider>
<ToastContainer />
<YourApp />
</Provider>
);
}Form fields
表单字段
S2 form fields render their own label, description, error message, and required indicator. Pass those as props on the field — don't wrap the field in a // to attach them.
<label><p><div>- — visible label.
label="..." - — help text.
description="..." - (or a function) — validation error; pair with
errorMessage="..."/isInvalid/validate.validationBehavior - — marks required and shows the indicator.
isRequired - — how required/optional is shown.
necessityIndicator="icon" | "label" - — help popover next to the label.
contextualHelp={<ContextualHelp>…</ContextualHelp>} - — when no visible label is needed.
aria-label="..."
tsx
<TextField type="email" label="Email" description="We'll never share it." isRequired />Group related fields with . It handles label alignment (, ), default sizes, the required indicator, and submission/validation flow. is not limited to a single vertical column — wrap subsets of fields in a styled native element to build multi-column or grid layouts.
FormlabelPositionlabelAlignFormS2 表单字段会自行渲染标签、描述、错误信息和必填指示器。请将这些内容作为属性传递给字段——不要将字段包裹在 // 中来附加这些内容。
<label><p><div>- —— 可见标签。
label="..." - —— 帮助文本。
description="..." - (或函数)—— 验证错误;与
errorMessage="..."/isInvalid/validate搭配使用。validationBehavior - —— 标记为必填并显示指示器。
isRequired - —— 必填/可选状态的展示方式。
necessityIndicator="icon" | "label" - —— 标签旁边的帮助弹层。
contextualHelp={<ContextualHelp>…</ContextualHelp>} - —— 不需要可见标签时使用。
aria-label="..."
tsx
<TextField type="email" label="Email" description="We'll never share it." isRequired />使用 对相关字段进行分组。它会处理标签对齐(、)、默认尺寸、必填指示器以及提交/验证流程。 不限于单列垂直布局——可以将字段子集包裹在带样式的原生元素中,以构建多列或网格布局。
FormlabelPositionlabelAlignFormIcons
图标
Use S2's built-in icons and illustrations.
- Import icons from , illustrations from
@react-spectrum/s2/icons/....@react-spectrum/s2/illustrations/... - Don't introduce third-party icon libraries (,
lucide-react,phosphor-icons, etc.).heroicons - Look up icons in the Icons catalog (or the S2 MCP tool if available). The catalog is the source of truth.
search_s2_icons - Don't grep or the S2 source — slow, often misses the intended name, finds stale/internal matches.
node_modules - Search the full catalog; don't settle for a partial name match. ≠
Heart;HeartBroken≠Edit.EditIn
tsx
import AlertTriangle from '@react-spectrum/s2/icons/AlertTriangle';
import DropToUpload from '@react-spectrum/s2/illustrations/gradient/generic1/DropToUpload';
import Warning from '@react-spectrum/s2/illustrations/linear/Warning';Illustrations come in Gradient (Generic 1 / Generic 2 variants) and Linear styles.
Commonly used icons: , , , , , , , , , , , , , .
AlertTriangleCloseChevronDownCheckmarkPreviewCheckmarkCircleAddChevronUpDataFileTextInfoCircleOpenInChatCodeSee Icons and Illustrations for the full catalogs.
使用 S2 内置的图标和插画。
- 从 导入图标,从
@react-spectrum/s2/icons/...导入插画。@react-spectrum/s2/illustrations/... - 不要引入第三方图标库(、
lucide-react、phosphor-icons等)。heroicons - 在图标目录中查找图标(如果可用,也可以使用 S2 MCP 的 工具)。目录是唯一可信来源。
search_s2_icons - 不要 grep 或 S2 源码——速度慢,经常找不到目标名称,还会匹配到过时的/内部的内容。
node_modules - 搜索完整目录;不要满足于部分名称匹配。≠
Heart;HeartBroken≠Edit。EditIn
tsx
import AlertTriangle from '@react-spectrum/s2/icons/AlertTriangle';
import DropToUpload from '@react-spectrum/s2/illustrations/gradient/generic1/DropToUpload';
import Warning from '@react-spectrum/s2/illustrations/linear/Warning';插画有渐变(Gradient,含 Generic 1 / Generic 2 变体)和线性(Linear)两种风格。
常用图标:、、、、、、、、、、、、、。
AlertTriangleCloseChevronDownCheckmarkPreviewCheckmarkCircleAddChevronUpDataFileTextInfoCircleOpenInChatCode完整目录请参见图标和插画。
Verify before declaring done
完成前的验证步骤
Before reporting the task as complete, exercise the project's own toolchain. The macro performs build-time checks that the editor alone won't show.
style- Typecheck. Run the project's typecheck (,
tsc --noEmit, etc.). Fix everything — wrongtsc -bvalues, missing required props, raw CSS in the macro all surface here.size - Build or dev server. Run at least once. The macro's "cannot statically evaluate" error means a value inside depends on something non-literal; refactor to use runtime conditions or the runtime style function.
style({...}) - Runtime warnings. If you can render the page, check the console for missing /
aria-label, deprecated props, etc. Treat these as failures.textValue
在报告任务完成之前,请运行项目自身的工具链进行验证。 宏会执行构建时检查,这些检查仅靠编辑器是无法发现的。
style- 类型检查。 运行项目的类型检查命令(、
tsc --noEmit等)。修复所有问题——错误的tsc -b值、缺失的必填属性、宏中的原生 CSS 都会在这里暴露出来。size - 构建或开发服务器。 至少运行一次。宏报出的「无法静态求值」错误意味着 内部的某个值依赖于非字面量内容;请重构代码,使用运行时条件或运行时样式函数。
style({...}) - 运行时警告。 如果可以渲染页面,请检查控制台是否有缺失 /
aria-label、废弃属性等警告。将这些警告视为失败项。textValue
Documentation Structure
文档结构
The directory contains detailed documentation organized as follows:
references/references/Guides
指南
- Component Decision Tree: How to choose the right S2 component when requirements do not name one explicitly.
- Implementing Figma designs with React Spectrum S2: How to translate Figma designs (via the Figma MCP) into S2 components and the macro.
style - Creating Custom Components: How to build custom Spectrum 2 components using React Aria Components and the macro.
style - Testing with React Spectrum S2: How to write tests for S2 components using ARIA pattern testers from .
@react-spectrum/test-utils - Collections
- Drag and Drop
- Forms
- Getting started
- Migrating to Spectrum 2
- Selection
- Style Macro
- Styling
- Testing
- Working with AI
- 组件决策树:当需求未明确指定组件时,如何选择合适的 S2 组件。
- 使用 React Spectrum S2 实现 Figma 设计:如何(通过 Figma MCP)将 Figma 设计转化为 S2 组件和 宏代码。
style - 创建自定义组件:如何使用 React Aria Components 和 宏构建自定义 Spectrum 2 组件。
style - 使用 React Spectrum S2 进行测试:如何使用 中的 ARIA 模式测试器为 S2 组件编写测试。
@react-spectrum/test-utils - 集合组件
- 拖拽
- 表单
- 快速开始
- 迁移到 Spectrum 2
- 选择功能
- 样式宏
- 样式设置
- 测试
- 与 AI 协作
Components
组件
Component documentation is in — one Markdown file per component (e.g. ). Read the file for a component when you need its API, props, examples, or accessibility notes.
references/components/references/components/Button.mdAvailable components: Accordion, ActionBar, ActionButton, ActionButtonGroup, ActionMenu, Avatar, AvatarGroup, Badge, Breadcrumbs, Button, ButtonGroup, Calendar, Card, CardView, Checkbox, CheckboxGroup, ColorArea, ColorField, ColorSlider, ColorSwatch, ColorSwatchPicker, ColorWheel, ComboBox, ContextualHelp, DateField, DatePicker, DateRangePicker, Dialog, Disclosure, Divider, DropZone, Form, Icons, IllustratedMessage, Illustrations, Image, InlineAlert, LabeledValue, Link, LinkButton, ListView, Menu, Meter, NumberField, Picker, Popover, ProgressBar, ProgressCircle, Provider, RadioGroup, RangeCalendar, RangeSlider, SearchField, SegmentedControl, SelectBoxGroup, SideNav, Skeleton, Slider, StatusLight, Switch, TableView, Tabs, TagGroup, TextArea, TextField, TimeField, Toast, ToggleButton, ToggleButtonGroup, Tooltip, TreeView.
组件文档位于 目录下——每个组件对应一个 Markdown 文件(例如 )。当需要了解组件的 API、属性、示例或无障碍说明时,请阅读对应的文件。
references/components/references/components/Button.md可用组件:Accordion, ActionBar, ActionButton, ActionButtonGroup, ActionMenu, Avatar, AvatarGroup, Badge, Breadcrumbs, Button, ButtonGroup, Calendar, Card, CardView, Checkbox, CheckboxGroup, ColorArea, ColorField, ColorSlider, ColorSwatch, ColorSwatchPicker, ColorWheel, ComboBox, ContextualHelp, DateField, DatePicker, DateRangePicker, Dialog, Disclosure, Divider, DropZone, Form, Icons, IllustratedMessage, Illustrations, Image, InlineAlert, LabeledValue, Link, LinkButton, ListView, Menu, Meter, NumberField, Picker, Popover, ProgressBar, ProgressCircle, Provider, RadioGroup, RangeCalendar, RangeSlider, SearchField, SegmentedControl, SelectBoxGroup, SideNav, Skeleton, Slider, StatusLight, Switch, TableView, Tabs, TagGroup, TextArea, TextField, TimeField, Toast, ToggleButton, ToggleButtonGroup, Tooltip, TreeView.
Additional References
其他参考
- React Aria Components: Documentation for unstyled accessible primitives. Use only when no React Spectrum S2 component fits the requirements.
- React Aria Components:无样式的可访问原语文档。仅在没有符合需求的 React Spectrum S2 组件时使用。