react-spectrum-s2

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React 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 (
get_design_context
,
search_design_system
, etc.) is available — consult Implementing Figma designs with React Spectrum S2 before generating code.
When writing tests that exercise S2 components, consult Testing with React Spectrum S2 and prefer the ARIA pattern testers from
@react-spectrum/test-utils
over hand-rolled role/selector queries.
如果需求未明确指定使用哪个 React Spectrum 组件,请先查阅组件决策树再选择组件。
如果需求涉及 Figma 设计、画框或 URL——或者可用 Figma MCP(
get_design_context
、
search_design_system
等)——请先查阅使用 React Spectrum S2 实现 Figma 设计再生成代码。
编写测试 S2 组件的用例时,请查阅使用 React Spectrum S2 进行测试,优先使用
@react-spectrum/test-utils
中的 ARIA 模式测试器,而非手动编写的 role/选择器查询。

React 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
    style
    macro for Spectrum styling.
  • You need a utility such as
    FocusScope
    ,
    VisuallyHidden
    ,
    useFocusRing
    ,
    mergeProps
    , etc.
The React Aria Components documentation is bundled under
references/react-aria/
. 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.
React Spectrum S2 构建于 React Aria Components 之上。S2 组件在无样式的 React Aria 原语基础上,添加了 Spectrum 2 的样式、行为和插槽结构。进行 React Spectrum 开发时,请始终优先使用 S2 组件,因为它们已预设样式、符合设计系统规范,且覆盖了绝大多数常见 UI 模式。
仅在以下情况直接使用 React Aria Components:
  • 没有符合需求的 S2 组件,需要构建自定义组件。请遵循创建自定义组件,将 React Aria 原语与 S2 的
    style
    宏结合使用以实现 Spectrum 样式。
  • 需要
    FocusScope
    、
    VisuallyHidden
    、
    useFocusRing
    、
    mergeProps
    等工具。
React Aria Components 文档打包在
references/react-aria/
目录下。许多无样式的 React Aria Components 与 S2 组件同名,因此请确保在需要时搜索并访问正确的文档。

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
@react-spectrum/s2
— prefer importing from there instead of
react-aria-components
,
react-stately
, or
@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/s2
重新导出——优先从该包导入,而非从
react-aria-components
、
react-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
Item
component.
Menu
→
MenuItem
/
MenuSection
;
Picker
→
PickerItem
/
PickerSection
;
ComboBox
→
ComboBoxItem
/
ComboBoxSection
;
ListView
→
ListViewItem
;
TreeView
→
TreeViewItem
(with
TreeViewItemContent
);
TableView
→
Row
/
Column
/
Cell
/
TableHeader
/
TableBody
;
SegmentedControl
→
SegmentedControlItem
;
TagGroup
→
Tag
;
Breadcrumbs
→
Breadcrumb
;
Accordion
→
AccordionItem
(with
AccordionItemHeader
/
AccordionItemTitle
/
AccordionItemPanel
).
每个 S2 集合组件都有自己的 item 导出——不存在通用的
Item
组件。对应关系如下:
Menu
→
MenuItem
/
MenuSection
;
Picker
→
PickerItem
/
PickerSection
;
ComboBox
→
ComboBoxItem
/
ComboBoxSection
;
ListView
→
ListViewItem
;
TreeView
→
TreeViewItem
(搭配
TreeViewItemContent
);
TableView
→
Row
/
Column
/
Cell
/
TableHeader
/
TableBody
;
SegmentedControl
→
SegmentedControlItem
;
TagGroup
→
Tag
;
Breadcrumbs
→
Breadcrumb
;
Accordion
→
AccordionItem
(搭配
AccordionItemHeader
/
AccordionItemTitle
/
AccordionItemPanel
)。

Styling

样式

Use S2 components and the S2
style
macro as the default styling approach.
  • Prefer S2 components first; use their
    styles
    prop only for layout-style properties.
  • For generic layouts (flex, grid, etc.), use native HTML elements with the
    style
    macro.
  • Avoid using Tailwind,
    radix-ui
    ,
    shadcn/ui
    , or any other third-party design system in S2 implementations.
  • IMPORTANT: avoid using
    UNSAFE_style
    and
    UNSAFE_className
    .
S2 components take a
styles
prop (plural) restricted to layout properties:
  • margin
    ,
    marginStart
    ,
    marginEnd
    ,
    marginTop
    ,
    marginBottom
    ,
    marginX
    ,
    marginY
  • width
    ,
    minWidth
    ,
    maxWidth
  • flexGrow
    ,
    flexShrink
    ,
    flexBasis
  • justifySelf
    ,
    alignSelf
    ,
    order
  • gridArea
    ,
    gridRow
    ,
    gridRowStart
    ,
    gridRowEnd
    ,
    gridColumn
    ,
    gridColumnStart
    ,
    gridColumnEnd
  • position
    ,
    zIndex
    ,
    top
    ,
    bottom
    ,
    inset
    ,
    insetX
    ,
    insetY
    ,
    insetStart
    ,
    insetEnd
  • visibility
  • height
    ,
    minHeight
    ,
    maxHeight
    (only in specific components without an intrinsic height)
tsx
<Button styles={style({marginStart: 8})}>Edit</Button>
Native HTML elements and React Aria Components take
className={style(...)}
— there the macro is unrestricted:
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 (
0
,
2
,
4
,
8
,
12
,
16
, …).
默认使用 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
    ,
    marginX
    ,
    marginY
  • width
    ,
    minWidth
    ,
    maxWidth
  • flexGrow
    ,
    flexShrink
    ,
    flexBasis
  • justifySelf
    ,
    alignSelf
    ,
    order
  • gridArea
    ,
    gridRow
    ,
    gridRowStart
    ,
    gridRowEnd
    ,
    gridColumn
    ,
    gridColumnStart
    ,
    gridColumnEnd
  • position
    ,
    zIndex
    ,
    top
    ,
    bottom
    ,
    inset
    ,
    insetX
    ,
    insetY
    ,
    insetStart
    ,
    insetEnd
  • 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 网格体系(
0
、
2
、
4
、
8
、
12
、
16
……)。

One
style({...})
call per element

每个元素仅调用一次
style({...})

Express runtime variation inside a single
style({...})
call using its conditional system, not by producing multiple style strings and combining them on the element:
  • Conditional keys can be nested objects (
    variant: {primary: ..., secondary: ...}
    ) or boolean conditions starting with
    is
    /
    allows
    (
    isSelected
    ,
    allowsRemoving
    ,
    isHovered
    ). The macro returns a function — call it with the runtime values:
    styles({variant, isSelected})
    .
  • Don't concatenate macro results via template literals,
    clsx
    ,
    classnames
    , or string spaces — the class names encode style precedence and concatenation breaks it. Use
    mergeStyles
    if you genuinely need to merge two separate style strings at runtime.
  • Don't combine
    className={style({...})}
    (or
    styles
    ) with an inline
    style={{...}}
    on the same element. The inline prop bypasses tokens and breaks layer ordering. Use the inline
    style
    prop only for values that genuinely can't be known at build time (e.g. a drag-handler position).
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
    )和内联
    style={{...}}
    。内联属性会绕过 token 系统并破坏层级顺序。仅当值确实无法在构建时确定时(例如拖拽处理器的位置),才使用内联
    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
    :
    'full'
    (not
    '100%'
    ),
    'screen'
    , or a number.
  • alignItems
    /
    justifyContent
    /
    alignSelf
    /
    justifySelf
    :
    'start'
    /
    'center'
    /
    'end'
    /
    'space-between'
    (not
    'flex-start'
    /
    'flex-end'
    ).
  • Spacing (
    margin
    ,
    padding
    ,
    gap
    ,
    top
    , etc.): numeric pixels on the 4px grid, not strings like
    '1rem'
    .
  • borderRadius
    :
    'none'
    ,
    'sm'
    ,
    'default'
    ,
    'lg'
    ,
    'xl'
    ,
    'full'
    ,
    'pill'
    . There is no
    'md'
    .
  • Colors: token names like
    'gray-100'
    ,
    'accent'
    ,
    'neutral'
    ,
    'neutral-subdued'
    — not hex,
    rgb()
    , or
    var(--…)
    .
  • Fonts:
    font: 'heading-xl'
    ,
    font: 'body-sm'
    , etc. — not CSS font shorthand.
  • Use logical sides —
    paddingStart
    /
    paddingEnd
    /
    marginStart
    /
    marginEnd
    /
    insetStart
    /
    insetEnd
    /
    borderStartStartRadius
    — not physical
    paddingLeft
    /
    paddingRight
    /
    marginLeft
    /
    marginRight
    /
    borderTopLeftRadius
    . The logical variants flip correctly under RTL.
Prefer semantic color tokens when the color carries meaning:
'accent'
,
'neutral'
,
'negative'
,
'positive'
,
'informative'
,
'notice'
for errors/success/info/warning/brand — not
'red-…'
/
'green-…'
/
'blue-…'
/
'orange-…'
. Reach for raw hue tokens only for decorative or chart colors.
该宏是一个类型化的 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
    、
    top
    等):4px 网格体系下的数字像素值,而非
    '1rem'
    之类的字符串。
  • borderRadius
    :取值为
    'none'
    、
    'sm'
    、
    'default'
    、
    'lg'
    、
    'xl'
    、
    'full'
    、
    'pill'
    ,没有
    'md'
    。
  • 颜色:使用 token 名称,如
    'gray-100'
    、
    'accent'
    、
    'neutral'
    、
    'neutral-subdued'
    ——而非十六进制值、
    rgb()
    或
    var(--…)
    。
  • 字体:使用
    font: 'heading-xl'
    、
    font: 'body-sm'
    等形式——而非 CSS 字体简写。
  • 使用逻辑边距——
    paddingStart
    /
    paddingEnd
    /
    marginStart
    /
    marginEnd
    /
    insetStart
    /
    insetEnd
    /
    borderStartStartRadius
    ——而非物理边距
    paddingLeft
    /
    paddingRight
    /
    marginLeft
    /
    marginRight
    /
    borderTopLeftRadius
    。逻辑属性在 RTL(从右到左)布局下会正确翻转。
当颜色具有语义含义时,优先使用语义化颜色 token:错误/成功/信息/警告/品牌色分别使用
'accent'
、
'neutral'
、
'negative'
、
'positive'
、
'informative'
、
'notice'
——而非
'red-…'
/
'green-…'
/
'blue-…'
/
'orange-…'
。仅在装饰性或图表颜色中使用原始色相 token。

Don't restate default prop values

不要重复设置默认属性值

variant="primary"
on
Button
,
size="M"
on most components,
density="regular"
on collections — setting a prop to its default is noise. Omit it.
Button
的
variant="primary"
、大多数组件的
size="M"
、集合组件的
density="regular"
——将属性设置为默认值属于冗余内容,请省略。

Responsive design

响应式设计

Layouts should adapt to viewport size. The
style
macro provides built-in breakpoint conditions on layout properties — use them rather than producing a fixed-width design.
Breakpoints (min-width, mobile-first):
xs
480,
sm
640,
md
768,
lg
1024,
xl
1280,
2xl
1536. The
default
value applies on the smallest viewport; each breakpoint takes over above its threshold.
tsx
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
宏在布局属性上提供了内置的断点条件——请使用这些条件,而非设计固定宽度的布局。
断点(最小宽度,移动优先):
xs
480、
sm
640、
md
768、
lg
1024、
xl
1280、
2xl
1536。
default
值适用于最小视口;每个断点在超过其阈值后生效。
tsx
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>
    ,
    <section>
    , etc. Use the slot components the component documents:
    • TreeView
      rows:
      TreeViewItemContent
      , not a
      div
      .
    • ListView
      ,
      Menu
      ,
      Picker
      ,
      ComboBox
      ,
      Tabs
      , etc.:
      Text
      (with
      slot="label"
      /
      slot="description"
      where relevant) plus documented icon/image/avatar slots.
    • Card
      ,
      AssetCard
      ,
      UserCard
      ,
      ProductCard
      : prescribed slot components (
      CardPreview
      ,
      Image
      ,
      Content
      ,
      Text
      ,
      Footer
      , etc.).
  • 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"
      /
      slot="description"
      )以及文档中规定的 icon/image/avatar 插槽。
    • Card
      、
      AssetCard
      、
      UserCard
      、
      ProductCard
      :使用指定的插槽组件(
      CardPreview
      、
      Image
      、
      Content
      、
      Text
      、
      Footer
      等)。
  • 插槽内部的额外布局应放在插槽组件内部,而非包裹在插槽组件外面。
  • 在组合组件之前,请阅读其文档页面的 API 部分——其中列出了支持的子组件、插槽和约束条件。文档示例是唯一可信来源。

Buttons with text and icon

带文本和图标的按钮

Button
/
ActionButton
/
LinkButton
with both an icon and a text label require the label to be wrapped in
<Text>
— plain string children next to an icon render incorrectly. (Icon-only children render fine but require
aria-label
.)
Text
is re-exported from each button's own subpath:
tsx
import {ActionButton, Text} from '@react-spectrum/s2/ActionButton';
import Download from '@react-spectrum/s2/icons/Download';

<ActionButton>
  <Download />
  <Text>Download</Text>
</ActionButton>
同时包含图标和文本标签的
Button
/
ActionButton
/
LinkButton
需要将标签包裹在
<Text>
中——图标旁边的纯字符串子元素会渲染错误。(仅含图标的子元素可以正常渲染,但需要设置
aria-label
。)
Text
会从每个按钮自身的子路径中重新导出:
tsx
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:
TableView
Cell
; Dialog/Popover bodies;
Disclosure
/
Accordion
panels. Compose these with native elements + the
style
macro like any page section. For any other component, defer to slot components.
少数组件明确支持自由形式的内容:
TableView
的
Cell
;Dialog/Popover 的主体;
Disclosure
/
Accordion
的面板。可以像处理页面区块一样,用原生元素 +
style
宏来组合这些组件的内容。对于其他任何组件,请遵循插槽组件的使用规范。

Only use
slot
values the component documents

仅使用组件文档中规定的
slot
值

Only pass
slot
when the parent's docs name that slot.
  • Don't put
    slot="close"
    on a
    Button
    inside a
    Dialog
    ButtonGroup
    — the dialog handles its own close;
    slot="close"
    on something else causes the parent context to hide it.
  • Don't sprinkle
    slot="label"
    /
    slot="description"
    on
    Text
    outside a component that exposes those slots.
仅当父组件的文档中明确列出了该插槽时,才传递
slot
属性。
  • 不要在
    Dialog
    的
    ButtonGroup
    内的
    Button
    上设置
    slot="close"
    ——对话框会自行处理关闭逻辑;在其他元素上设置
    slot="close"
    会导致父上下文将其隐藏。
  • 不要在未暴露对应插槽的组件外部的
    Text
    上随意添加
    slot="label"
    /
    slot="description"
    。

Don't reinvent
Card
/
CardView

不要自行实现
Card
/
CardView

For grids of objects/files/products/people, use
CardView
plus a prescribed variant (
AssetCard
,
UserCard
,
ProductCard
) or
Card
composed with
CardPreview
/
Content
/
Text
/
Footer
. Don't emit hand-rolled card divs or
<article>
wrappers. Build a custom card with the documented slot components only when no variant fits.
对于对象/文件/产品/人物的网格布局,请使用
CardView
搭配指定的变体(
AssetCard
、
UserCard
、
ProductCard
),或使用由
CardPreview
/
Content
/
Text
/
Footer
组合而成的
Card
。不要手动编写卡片的 div 或
<article>
包装器。仅当没有合适的变体时,才使用文档中规定的插槽组件构建自定义卡片。

Collection components handle their own scrolling

集合组件自行处理滚动

TableView
,
ListView
,
TreeView
,
CardView
,
Menu
, and
ListBox
virtualize and scroll internally. Don't wrap them in an
overflow
/
overflowY
/
overflowX
container — that produces a nested scroller and breaks keyboard navigation. Give the collection a bounded
height
/
maxHeight
via its
styles
prop instead.
tsx
// ❌ 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>
TableView
、
ListView
、
TreeView
、
CardView
、
Menu
和
ListBox
内部实现了虚拟化和滚动。不要将它们包裹在设置了
overflow
/
overflowY
/
overflowX
的容器中——这会产生嵌套滚动条并破坏键盘导航。请通过
styles
属性为集合组件设置有界的
height
/
maxHeight
。
tsx
// ❌ 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 (
Menu
,
Picker
,
ComboBox
,
ListView
,
TreeView
,
TableView
,
CardView
,
SegmentedControl
,
TagGroup
,
Breadcrumbs
, etc.) share a small set of conventions. Getting them wrong causes runtime warnings, broken selection, and broken keyboard navigation.
集合组件(
Menu
、
Picker
、
ComboBox
、
ListView
、
TreeView
、
TableView
、
CardView
、
SegmentedControl
、
TagGroup
、
Breadcrumbs
等)遵循一套通用的约定。违反这些约定会导致运行时警告、选择功能失效和键盘导航异常。

Every item needs an
id

每个项都需要
id

Items use
id
for selection,
onAction(key)
, sort, expansion, and React reconciliation. Static items get a literal
id
; dynamic items get
id={item.something}
inside the render function. When using
.map
, set both
id
and React's
key
:
tsx
// ✅ 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
items
prop and using a render function, only
id
is needed.
项的
id
用于选择、
onAction(key)
、排序、展开和 React 的协调过程。静态项使用字面量
id
;动态项在渲染函数中设置
id={item.something}
。使用
.map
时,请同时设置
id
(供集合组件使用)和 React 的
key
:
tsx
// ✅ 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>
通过
items
属性传递数据并使用渲染函数时,仅需设置
id
。

textValue
when item children aren't plain text

当项的子元素不是纯文本时设置
textValue

If an item's children include anything other than a plain string (icon, avatar, multi-slot layout, custom component), set
textValue="..."
. The collection uses it for screen-reader announcements, typeahead, and drag-and-drop labels. Omitting it produces a runtime warning.
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

Every collection (
ListView
,
TableView
,
CardView
,
TreeView
,
Menu
,
ListBox
,
GridList
,
TagGroup
,
Breadcrumbs
) needs an accessible name —
aria-label="..."
or
aria-labelledby="..."
.
每个集合组件(
ListView
、
TableView
、
CardView
、
TreeView
、
Menu
、
ListBox
、
GridList
、
TagGroup
、
Breadcrumbs
)都需要一个可访问名称——即
aria-label="..."
或
aria-labelledby="..."
。

Empty and loading states are built in

内置空状态和加载状态

  • Empty state: pass
    renderEmptyState
    returning an
    IllustratedMessage
    . Don't conditionally swap the whole collection for a custom empty
    div
    .
  • Async data: use
    useAsyncList
    (or the user's preferred data fetching library) plus the collection's
    loadingState
    /
    onLoadMore
    props. Don't render a separate spinner.
  • 空状态:传入返回
    IllustratedMessage
    的
    renderEmptyState
    。不要有条件地将整个集合替换为自定义的空
    div
    。
  • 异步数据:使用
    useAsyncList
    (或用户偏好的数据请求库)搭配集合组件的
    loadingState
    /
    onLoadMore
    属性。不要单独渲染加载指示器。

Bulk actions with ActionBar

使用 ActionBar 实现批量操作

Use the
renderActionBar
prop on a collection to show an
ActionBar
when items are selected. The collection passes the current
selectedKeys
to the callback and wires up count and clear-selection automatically — don't pass
selectedItemCount
or
onClearSelection
to
ActionBar
manually.
在集合组件上使用
renderActionBar
属性,以便在选中项时显示
ActionBar
。集合组件会将当前的
selectedKeys
传递给回调函数,并自动关联计数和清除选择功能——不要手动向
ActionBar
传递
selectedItemCount
或
onClearSelection
。

Typography

排版

Text
,
Heading
, and
Content
are slot components — they only produce the right typography inside an S2 component that provides their slot context (cards, dialogs, lists, pickers, menus, tabs, etc.). Outside those contexts, use a native HTML element with the
style
macro.
tsx
// ✅ 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
font
shorthand sets
fontFamily
,
fontSize
,
fontWeight
,
lineHeight
, and a default
color
. Don't add a redundant
color
next to a
font
value unless you actually want to override the default.
Text
、
Heading
和
Content
是插槽组件——它们只有在提供插槽上下文的 S2 组件(卡片、对话框、列表、选择器、菜单、标签页等)内部才能呈现正确的排版样式。在这些上下文之外,请使用原生 HTML 元素搭配
style
宏。
tsx
// ✅ 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>
font
简写属性会设置
fontFamily
、
fontSize
、
fontWeight
、
lineHeight
以及默认的
color
。除非确实需要覆盖默认值,否则不要在
font
值旁边添加多余的
color
。

Font scales — pick by role, not size

字体缩放——按角色选择,而非大小

  • heading-*
    — page/section headings (
    h1
    –
    h6
    ); tracks the document outline.
  • title-*
    — titles inside components (card titles, dialog titles, tile labels) that aren't part of the page outline.
  • body-*
    — running prose and long-form copy.
  • detail-*
    — supporting metadata, captions, timestamps, footnotes.
  • ui-*
    — interactive UI text: buttons, menu items, form labels, table cells.
  • code-*
    — inline or block code (monospace family).
Sizes are
-xs
,
-sm
, default (no suffix),
-lg
,
-xl
,
-2xl
,
-3xl
(not every family has every size — see the Style Macro reference).
  • heading-*
    —— 页面/章节标题(
    h1
    –
    h6
    );对应文档大纲结构。
  • title-*
    —— 组件内部的标题(卡片标题、对话框标题、磁贴标签),不属于页面大纲的一部分。
  • body-*
    —— 正文段落和长文本内容。
  • detail-*
    —— 辅助元数据、说明文字、时间戳、脚注。
  • ui-*
    —— 交互式 UI 文本:按钮、菜单项、表单标签、表格单元格。
  • code-*
    —— 行内或块级代码(等宽字体族)。
尺寸包括
-xs
、
-sm
、默认(无后缀)、
-lg
、
-xl
、
-2xl
、
-3xl
(并非每个字体族都支持所有尺寸——参见样式宏参考文档)。

Provider scope

Provider 作用域

Mount a single
Provider
at the application root (inside
body
, around the top-level layout).
  • Don't wrap
    <html>
    ,
    <head>
    , or
    <body>
    with
    Provider
    .
  • Don't add a
    Provider
    around a Dialog, Popover, Toast, or other portaled overlay — they inherit through context.
  • Don't stack
    Provider
    s. Nesting is only correct when scoping a different
    locale
    /
    router
    /
    colorScheme
    to a subtree, which is rare.
  • Let the
    Provider
    manage
    colorScheme
    . Don't hard-code
    colorScheme="light"
    or
    colorScheme="dark"
    to make a screenshot match — for one-off light/dark color differences, use
    lightDark()
    .
If your app uses
ToastQueue
, place a single
<ToastContainer />
as a sibling of the root layout, inside
Provider
.
tsx
import {Provider} from '@react-spectrum/s2/Provider';
import {ToastContainer} from '@react-spectrum/s2/Toast';

function App() {
  return (
    <Provider>
      <ToastContainer />
      <YourApp />
    </Provider>
  );
}
在应用根节点挂载单个
Provider
(位于
body
内部,包裹顶层布局)。
  • 不要用
    Provider
    包裹
    <html>
    、
    <head>
    或
    <body>
    。
  • 不要在 Dialog、Popover、Toast 或其他 portal 弹层外面添加
    Provider
    ——它们会通过 context 继承样式。
  • 不要堆叠
    Provider
    。仅当需要为子树设置不同的
    locale
    /
    router
    /
    colorScheme
    时才允许嵌套,这种情况很少见。
  • 让
    Provider
    管理
    colorScheme
    。不要为了让截图匹配而硬编码
    colorScheme="light"
    或
    colorScheme="dark"
    ——对于一次性的亮/暗颜色差异,请使用
    lightDark()
    。
如果你的应用使用
ToastQueue
,请将单个
<ToastContainer />
放在
Provider
内部,作为根布局的同级元素。
tsx
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
<label>
/
<p>
/
<div>
to attach them.
  • label="..."
    — visible label.
  • description="..."
    — help text.
  • errorMessage="..."
    (or a function) — validation error; pair with
    isInvalid
    /
    validate
    /
    validationBehavior
    .
  • isRequired
    — marks required and shows the indicator.
  • necessityIndicator="icon" | "label"
    — how required/optional is shown.
  • contextualHelp={<ContextualHelp>…</ContextualHelp>}
    — help popover next to the label.
  • aria-label="..."
    — when no visible label is needed.
tsx
<TextField type="email" label="Email" description="We'll never share it." isRequired />
Group related fields with
Form
. It handles label alignment (
labelPosition
,
labelAlign
), default sizes, the required indicator, and submission/validation flow.
Form
is not limited to a single vertical column — wrap subsets of fields in a styled native element to build multi-column or grid layouts.
S2 表单字段会自行渲染标签、描述、错误信息和必填指示器。请将这些内容作为属性传递给字段——不要将字段包裹在
<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 />
使用
Form
对相关字段进行分组。它会处理标签对齐(
labelPosition
、
labelAlign
)、默认尺寸、必填指示器以及提交/验证流程。
Form
不限于单列垂直布局——可以将字段子集包裹在带样式的原生元素中,以构建多列或网格布局。

Icons

图标

Use S2's built-in icons and illustrations.
  • Import icons from
    @react-spectrum/s2/icons/...
    , illustrations from
    @react-spectrum/s2/illustrations/...
    .
  • Don't introduce third-party icon libraries (
    lucide-react
    ,
    phosphor-icons
    ,
    heroicons
    , etc.).
  • Look up icons in the Icons catalog (or the S2 MCP
    search_s2_icons
    tool if available). The catalog is the source of truth.
  • Don't grep
    node_modules
    or the S2 source — slow, often misses the intended name, finds stale/internal matches.
  • 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:
AlertTriangle
,
Close
,
ChevronDown
,
Checkmark
,
Preview
,
CheckmarkCircle
,
Add
,
ChevronUp
,
Data
,
FileText
,
InfoCircle
,
OpenIn
,
Chat
,
Code
.
See 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
    node_modules
    或 S2 源码——速度慢,经常找不到目标名称,还会匹配到过时的/内部的内容。
  • 搜索完整目录;不要满足于部分名称匹配。
    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)两种风格。
常用图标:
AlertTriangle
、
Close
、
ChevronDown
、
Checkmark
、
Preview
、
CheckmarkCircle
、
Add
、
ChevronUp
、
Data
、
FileText
、
InfoCircle
、
OpenIn
、
Chat
、
Code
。
完整目录请参见图标和插画。

Verify before declaring done

完成前的验证步骤

Before reporting the task as complete, exercise the project's own toolchain. The
style
macro performs build-time checks that the editor alone won't show.
  • Typecheck. Run the project's typecheck (
    tsc --noEmit
    ,
    tsc -b
    , etc.). Fix everything — wrong
    size
    values, missing required props, raw CSS in the macro all surface here.
  • Build or dev server. Run at least once. The macro's "cannot statically evaluate" error means a value inside
    style({...})
    depends on something non-literal; refactor to use runtime conditions or the runtime style function.
  • Runtime warnings. If you can render the page, check the console for missing
    aria-label
    /
    textValue
    , deprecated props, etc. Treat these as failures.
在报告任务完成之前,请运行项目自身的工具链进行验证。
style
宏会执行构建时检查,这些检查仅靠编辑器是无法发现的。
  • 类型检查。 运行项目的类型检查命令(
    tsc --noEmit
    、
    tsc -b
    等)。修复所有问题——错误的
    size
    值、缺失的必填属性、宏中的原生 CSS 都会在这里暴露出来。
  • 构建或开发服务器。 至少运行一次。宏报出的「无法静态求值」错误意味着
    style({...})
    内部的某个值依赖于非字面量内容;请重构代码,使用运行时条件或运行时样式函数。
  • 运行时警告。 如果可以渲染页面,请检查控制台是否有缺失
    aria-label
    /
    textValue
    、废弃属性等警告。将这些警告视为失败项。

Documentation Structure

文档结构

The
references/
directory contains detailed documentation organized as follows:
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
    style
    macro.
  • Creating Custom Components: How to build custom Spectrum 2 components using React Aria Components and the
    style
    macro.
  • 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 和
    style
    宏构建自定义 Spectrum 2 组件。
  • 使用 React Spectrum S2 进行测试:如何使用
    @react-spectrum/test-utils
    中的 ARIA 模式测试器为 S2 组件编写测试。
  • 集合组件
  • 拖拽
  • 表单
  • 快速开始
  • 迁移到 Spectrum 2
  • 选择功能
  • 样式宏
  • 样式设置
  • 测试
  • 与 AI 协作

Components

组件

Component documentation is in
references/components/
— one Markdown file per component (e.g.
references/components/Button.md
). Read the file for a component when you need its API, props, examples, or accessibility notes.
Available 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.
组件文档位于
references/components/
目录下——每个组件对应一个 Markdown 文件(例如
references/components/Button.md
)。当需要了解组件的 API、属性、示例或无障碍说明时,请阅读对应的文件。
可用组件: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 组件时使用。