mcpField
R
react-spectrum-s2

react-spectrum-s2

github.com/zzci设计与 UI262 次安装3 天前更新
代码仓库

关于 react-spectrum-s2

为使用 React Spectrum S2 构建界面的编码 Agent 提供指导,涵盖组件选择、导入、样式、组合、集合、无障碍设计和响应式布局。

主要功能

  1. 01S2 组件选择指南
  2. 02子路径导入约定
  3. 03样式宏与令牌规则
  4. 04组件组合和集合模式
  5. 05无障碍与响应式布局指南

适用场景

  1. 01按界面需求选择 S2 组件
  2. 02使用 S2 实现 Figma 设计
  3. 03构建无障碍的 S2 集合
  4. 04为 S2 组件编写测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/zzci/skills --skill react-spectrum-s2

安装后在 Claude Code 中输入/react-spectrum-s2确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/components/
Accordion.md
ActionBar.md
ActionButton.md
ActionButtonGroup.md
ActionMenu.md
Avatar.md
AvatarGroup.md
Badge.md
Breadcrumbs.md
Button.md
ButtonGroup.md
Calendar.md
Card.md
CardView.md
Checkbox.md
CheckboxGroup.md
ColorArea.md
ColorField.md
ColorSlider.md
ColorSwatch.md
ColorSwatchPicker.md
ColorWheel.md
ComboBox.md
ContextualHelp.md
DateField.md
DatePicker.md
DateRangePicker.md
Dialog.md
Disclosure.md
Divider.md

详细介绍

React Spectrum S2(Spectrum 2)

如果需求没有明确指定要使用哪个 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/selector 查询。

React Spectrum S2 与 React Aria Components

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 组件同名,因此请确保在需要时搜索并访问正确的文档。

导入

请使用与文档匹配的子路径导入,不要从包入口 '@react-spectrum/s2' 导入:

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';

常用类型和列表数据钩子会从 @react-spectrum/s2 重新导出;请优先从此处导入,而不是从 react-aria-components、react-stately 或 @react-types/* 导入:

import type {Key, Selection, SortDescriptor, PressEvent, RangeValue, DateValue, DateRange, TimeValue, RouterConfig} from '@react-spectrum/s2';
import {useListData, useTreeData, useAsyncList} from '@react-spectrum/s2';
为每个集合使用对应的类型化 Item

每个 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)。

样式

默认使用 S2 组件和 S2 style 宏来设置样式。

  • 优先使用 S2 组件;其 styles 属性仅用于布局样式属性。
  • 对于通用布局(flex、grid 等),使用带有 style 宏的原生 HTML 元素。
  • 在 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(仅适用于没有固有高度的特定组件)
<Button styles={style({marginStart: 8})}>Edit</Button>

原生 HTML 元素和 React Aria Components 接受 className={style(...)},此处的宏不受限制:

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,……)。

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

使用 style({...}) 单次调用中的条件系统表达内部的运行时变化,不要生成多个样式字符串再在元素上组合:

  • 条件键可以是嵌套对象(variant: {primary: ..., secondary: ...}),也可以是以 is/allows 开头的布尔条件(isSelected、allowsRemoving、isHovered)。宏会返回一个函数——用运行时值调用它:styles({variant, isSelected})。
  • 不要通过模板字面量、clsx、classnames 或字符串空格拼接宏的结果——类名编码了样式优先级,拼接会破坏这一点。如果确实需要在运行时合并两个独立的样式字符串,请使用 mergeStyles。
  • 不要在同一个元素上同时使用 className={style({...})}(或 styles)和内联的 style={{...}}。内联属性会绕过令牌并破坏层级顺序。只有对于确实无法在构建时确定的值(例如拖动处理程序的位置),才使用内联 style 属性。
// ❌ 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})} />

如果某个值似乎无法通过宏表达,请先查阅 Style Macro 参考文档,再考虑使用内联样式——宏支持大多数 CSS 属性(网格布局、溢出、定位、尺寸、显示)。

样式宏的值是令牌,不是原始 CSS

宏采用类型化令牌系统。原始 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'。
  • 颜色:使用 '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 下会正确翻转。

当颜色具有语义时,优先使用语义颜色令牌:对于错误/成功/信息/警告/品牌,使用 'accent'、'neutral'、'negative'、'positive'、'informative'、'notice',而不是 'red-…'/'green-…'/'blue-…'/'orange-…'。仅在装饰或图表颜色场景下使用原始色相令牌。

不要重复声明默认属性值

Button 上的 variant="primary"、大多数组件上的 size="M"、集合上的 density="regular"——将属性设为默认值没有意义。省略即可。

响应式设计

布局应适应视口大小。style 宏在布局属性上内置了断点条件——应使用这些条件,而不是设计固定宽度布局。

断点(最小宽度,移动优先):xs 480、sm 640、md 768、lg 1024、xl 1280、2xl 1536。最小视口使用 default 值;超过各断点阈值后,则由对应断点接管。

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}
});

组件组合

S2 组件会定义自己的内部 DOM 和插槽结构。当组件要求特定的子组件 API 时,不要注入包装元素。

  • 不要用 <div>、<span>、<section> 等元素包装集合项的子元素。请使用组件文档中说明的插槽组件:
    • TreeView 行:使用 TreeViewItemContent,而不是 div。
    • ListView、Menu、Picker、ComboBox、Tabs 等:使用 Text(在相关情况下添加 slot="label"/slot="description"),以及文档中说明的图标/图片/头像插槽。
    • Card、AssetCard、UserCard、ProductCard:使用指定的插槽组件(CardPreview、Image、Content、Text、Footer 等)。
  • 插槽内的额外布局应放在插槽组件内部,而不是包在插槽组件外部。
  • 组合组件前,请阅读其文档页面的 API 部分——其中列出了预期的子组件、插槽和限制。文档示例是权威依据。
同时带文本和图标的按钮

同时带有图标和文本标签的 Button/ActionButton/LinkButton,要求将标签包在 <Text> 中——图标旁的纯字符串子元素无法正确渲染。(仅带图标的子元素可以正常渲染,但需要 aria-label。)Text 会从每个按钮自己的子路径重新导出:

import {ActionButton, Text} from '@react-spectrum/s2/ActionButton';
import Download from '@react-spectrum/s2/icons/Download';

<ActionButton>
  <Download />
  <Text>Download</Text>
</ActionButton>
例外:接受任意内容的组件

有少数组件明确支持自由形式的内容:TableView 的 Cell;Dialog/Popover 的主体;Disclosure/Accordion 面板。可以像页面的其他部分一样,使用原生元素和 style 宏组合这些内容。对于其他组件,请使用插槽组件。

仅使用组件文档中说明的 slot 值

只有当父组件的文档中列出该插槽时,才传递 slot。

  • 不要在 Dialog ButtonGroup 内的 Button 上设置 slot="close"——对话框会自行处理关闭;在其他元素上设置 slot="close" 会导致父级上下文将其隐藏。
  • 不要在未提供这些插槽的组件之外,随意给 Text 添加 slot="label"/slot="description"。
不要重新实现 Card/CardView

对于对象/文件/产品/人员的网格,请使用 CardView 和指定的变体(AssetCard、UserCard、ProductCard),或使用 Card 搭配 CardPreview/Content/Text/Footer。不要手动编写卡片 div 或 <article> 包装元素。只有在没有合适变体时,才使用文档中说明的插槽组件构建自定义卡片。

集合组件会自行处理滚动

TableView、ListView、TreeView、CardView、Menu 和 ListBox 会自行虚拟化并处理滚动。不要将它们包在 overflow/overflowY/overflowX 容器中——这会产生嵌套滚动区域,并破坏键盘导航。请通过组件的 styles 属性为集合设置有界的 height/maxHeight。

// ❌ 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>

集合

集合组件(Menu、Picker、ComboBox、ListView、TreeView、TableView、CardView、SegmentedControl、TagGroup、Breadcrumbs 等)遵循一小组通用约定。违反这些约定会导致运行时警告、选择功能异常和键盘导航失效。

每个项都需要 id

项使用 id 来处理选择、onAction(key)、排序、展开和 React 协调。静态项使用字面量 id;动态项在渲染函数中使用 id={item.something}。使用 .map 时,请同时设置 id 和 React 的 key:

// ✅ 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

如果项的子元素包含纯字符串以外的内容(图标、头像、多插槽布局、自定义组件),请设置 textValue="..."。集合会使用它生成屏幕阅读器播报、类型前缀搜索和拖放标签。省略该属性会产生运行时警告。

<ListViewItem id={item.id} textValue={item.name}>
  <FileIcon />
  <Text slot="label">{item.name}</Text>
  <Text slot="description">{item.size}</Text>
</ListViewItem>
在集合容器上设置 aria-label

每个集合(ListView、TableView、CardView、TreeView、Menu、ListBox、GridList、TagGroup、Breadcrumbs)都需要一个可访问名称——aria-label="..." 或 aria-labelledby="..."。

空状态和加载状态已内置
  • 空状态:传入返回 IllustratedMessage 的 renderEmptyState。不要有条件地将整个集合替换为自定义空状态 div。
  • 异步数据:使用 useAsyncList(或用户偏好的数据获取库),并配合集合的 loadingState/onLoadMore 属性。不要单独渲染加载指示器。
使用 ActionBar 执行批量操作

在集合上使用 renderActionBar 属性,在选中项目时显示 ActionBar。集合会将当前的 selectedKeys 传递给回调,并自动处理计数和清除选择;不要手动向 ActionBar 传入 selectedItemCount 或 onClearSelection。

字体排版

Text、Heading 和 Content 是插槽组件——只有在提供插槽上下文的 S2 组件(卡片、对话框、列表、选择器、菜单、标签页等)内,它们才会呈现正确的字体排版。在这些上下文之外,请使用带有 style 宏的原生 HTML 元素。

// ✅ 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。

字体比例——按用途选择,而非按字号
  • heading-*——页面/区块标题(h1–h6);与文档大纲保持一致。
  • title-*——组件内部的标题(卡片标题、对话框标题、磁贴标签),不属于页面大纲。
  • body-*——正文和长篇文案。
  • detail-*——辅助元数据、说明文字、时间戳、脚注。
  • ui-*——交互式 UI 文本:按钮、菜单项、表单标签、表格单元格。
  • code-*——行内或代码块(等宽字体)。

字号为 -xs、-sm、默认值(无后缀)、-lg、-xl、-2xl、-3xl(并非每种字体系列都包含所有字号——请参阅 Style Macro 参考)。

Provider 作用范围

在应用根部(body 内、顶层布局周围)挂载一个 Provider。

  • 不要用 Provider 包裹 <html>、<head> 或 <body>。
  • 不要在 Dialog、Popover、Toast 或其他通过 Portal 渲染的叠加层外添加 Provider——它们会通过上下文继承。
  • 不要堆叠多个 Provider。只有在子树范围内设置不同的 locale/router/colorScheme 时才应嵌套,这种情况很少见。
  • 让 Provider 管理 colorScheme。不要为了匹配截图而硬编码 colorScheme="light" 或 colorScheme="dark"——对于个别浅色/深色差异,请使用 lightDark()。

如果应用使用 ToastQueue,请在根布局旁、Provider 内放置一个 <ToastContainer />。

import {Provider} from '@react-spectrum/s2/Provider';
import {ToastContainer} from '@react-spectrum/s2/Toast';

function App() {
  return (
    <Provider>
      <ToastContainer />
      <YourApp />
    </Provider>
  );
}

表单字段

S2 表单字段会自行渲染标签、说明、错误消息和必填指示器。请将这些内容作为属性传给字段——不要用 <label>/<p>/<div> 包裹字段来附加它们。

  • label="..."——可见标签。
  • description="..."——帮助文本。
  • errorMessage="..."(或函数)——验证错误;与 isInvalid / validate / validationBehavior 配合使用。
  • isRequired——标记为必填并显示指示器。
  • necessityIndicator="icon" | "label"——指定如何显示必填/选填状态。
  • contextualHelp={<ContextualHelp>…</ContextualHelp>}——标签旁的帮助弹出框。
  • aria-label="..."——无需可见标签时使用。
<TextField type="email" label="Email" description="We'll never share it." isRequired />

使用 Form 将相关字段分组。它负责处理标签对齐(labelPosition、labelAlign)、默认尺寸、必填指示器以及提交/验证流程。Form 不限于单列纵向布局——可将部分字段包裹在经过样式设置的原生元素中,以构建多列或网格布局。

图标

使用 S2 内置的图标和插图。

  • 从 @react-spectrum/s2/icons/... 导入图标,从 @react-spectrum/s2/illustrations/... 导入插图。
  • 不要引入第三方图标库(lucide-react、phosphor-icons、heroicons 等)。
  • 在 图标 目录中查找图标(如果可用,也可以使用 S2 MCP search_s2_icons 工具)。该目录是唯一可信来源。
  • 不要在 node_modules 或 S2 源码中使用 grep——速度慢,而且经常找不到预期名称,反而会搜出过时或内部匹配项。
  • 搜索完整目录;不要满足于部分名称匹配。Heart ≠ HeartBroken;Edit ≠ EditIn。
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';

插图有渐变(Generic 1 / Generic 2 变体)和线性两种样式。

常用图标:AlertTriangle、Close、ChevronDown、Checkmark、Preview、CheckmarkCircle、Add、ChevronUp、Data、FileText、InfoCircle、OpenIn、Chat、Code。

完整目录请参阅图标和插图。

完成前验证

在报告任务完成之前,请运行项目自身的工具链。style 宏会执行仅靠编辑器无法发现的构建时检查。

  • 类型检查。 运行项目的类型检查(tsc --noEmit、tsc -b 等)。修复所有问题——错误的 size 值、缺少必需的属性、宏中的原始 CSS 等都会在这里暴露出来。
  • 构建或开发服务器。 至少运行一次。宏报错“cannot statically evaluate”表示 style({...}) 中的某个值依赖于非字面量;请重构为使用运行时条件或运行时样式函数。
  • 运行时警告。 如果可以渲染页面,请检查控制台中是否存在缺少 aria-label/textValue、属性已弃用等问题。请将这些问题视为失败。

文档结构

references/ 目录包含详细文档,组织如下:

指南
组件

组件文档位于 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。

补充参考
  • React Aria Components:无样式、支持无障碍访问的基础组件文档。仅当 React Spectrum S2 中没有符合要求的组件时使用。

评论

支持 Markdown