Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
react-spectrum.adobe.com avatar

React Spectrum S2

  • 841 installs
  • react-spectrum.adobe.com

React Spectrum S2 is an agent skill that supplies decision trees, Figma implementation guides, testing rules, and reference documentation so AI coding agents can correctly generate UIs with Adobe's Spectrum 2 React compo

About

React Spectrum S2 is an agent skill that equips AI coding agents with the exact decision logic, guides, and references needed to build user interfaces using Adobe's Spectrum 2 component library in React. It activates when a project uses @react-spectrum/s2 or when the builder wants to follow the Adobe design system. The skill surfaces a Component Decision Tree for ambiguous requirements, a Figma-to-S2 translation workflow that integrates with design MCPs, and official testing patterns that favor @react-spectrum/test-utils over custom queries. It also explains the layered relationship between the styled S2 components and the underlying unstyled React Aria primitives so agents consistently choose the right abstraction. This eliminates guesswork and ensures generated code respects Spectrum 2 styling, behavior, accessibility, and testing conventions from the first component.

  • Includes a Component Decision Tree that selects the correct React Spectrum S2 component when requirements are ambiguous
  • Provides a dedicated Figma-to-S2 implementation guide that works with Figma MCP servers
  • Supplies official test-utils guidance preferring ARIA pattern testers from @react-spectrum/test-utils
  • Clarifies the relationship between React Spectrum S2 (styled) and React Aria Components (unstyled primitives)
  • Delivers ready-to-use references and decision workflows for consistent Spectrum 2 adoption

React Spectrum S2 by the numbers

  • 841 all-time installs (skills.sh)
  • +32 installs in the week ending Aug 4, 2026 (Skillselion tracking)
  • Ranked #435 of 2,245 Frontend Development skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/react-spectrum.adobe.com --skill react-spectrum-s2

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs841
Security audit1 / 1 scanners passed
Repositoryreact-spectrum.adobe.com

What it does

Generate production-grade React UIs that follow Adobe Spectrum 2 design system rules when using @react-spectrum/s2.

Who is it for?

Best when you're working in React projects that require the Adobe Spectrum 2 design system or need to implement Figma designs using official S2 components.

Skip if: Projects not using React, projects avoiding design systems, or teams that have already built their own comprehensive internal component library outside of Spectrum.

When should I use this skill?

If the requirements do not clearly specify which React Spectrum component to use, or if the request involves a Figma design, or when writing tests that exercise S2 components.

What you get

The agent produces consistent, accessible, and correctly styled Spectrum 2 components that pass official test-utils and align with Adobe's design system, ready for immediate integration.

  • Correctly chosen and implemented Spectrum 2 components
  • Component decision documentation
  • Test files using official @react-spectrum/test-utils

By the numbers

  • 1 Component Decision Tree
  • 3 official reference guides
  • 2 component layer comparison

Files

SKILL.mdMarkdownGitHub ↗

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 S2 vs 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.

Imports

Use subpath imports matching the documentation — don't import from the package barrel '@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';

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/*:

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

Each S2 collection component has its own item export — there is no generic Item component. MenuMenuItem/MenuSection; PickerPickerItem/PickerSection; ComboBoxComboBoxItem/ComboBoxSection; ListViewListViewItem; TreeViewTreeViewItem (with TreeViewItemContent); TableViewRow/Column/Cell/TableHeader/TableBody; SegmentedControlSegmentedControlItem; TagGroupTag; BreadcrumbsBreadcrumb; AccordionAccordionItem (with 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)
<Button styles={style({marginStart: 8})}>Edit</Button>

Native HTML elements and React Aria Components take className={style(...)} — there the macro is unrestricted:

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

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

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).
// ❌ 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 macro values are tokens, not raw 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.

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.

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.

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.

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:

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.

Only use slot values the component documents

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.

Don't reinvent 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.

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.

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

Every item needs an 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:

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

textValue when item children aren't plain text

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.

<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

Every collection (ListView, TableView, CardView, TreeView, Menu, ListBox, GridList, TagGroup, Breadcrumbs) needs an accessible name — aria-label="..." or 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.

Bulk actions with 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.

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.

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

Font scales — pick by role, not size

  • heading-* — page/section headings (h1h6); 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).

Provider scope

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 Providers. 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.

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.
<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.

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. HeartHeartBroken; EditEditIn.
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.

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.

Documentation Structure

The references/ directory contains detailed documentation organized as follows:

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

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, 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.

Related skills

How it compares

Use this instead of generic React component prompts when you need Spectrum 2-specific decision logic, Figma translation rules, and test-utils guidance.

FAQ

Who is react-spectrum-s2 for?

Developers and developers using React who want their AI coding agents to produce consistent, accessible UIs that follow Adobe's Spectrum 2 design system without constant manual correction.

When should I use react-spectrum-s2?

Use it whenever requirements do not clearly specify which Spectrum component to pick, when you receive a Figma design that must be implemented with S2, when writing tests for S2 components, or when you need to decide between React Spectrum S2 and the underlying React Aria primiti

Is react-spectrum-s2 safe to install?

Yes. Users should review the Security Audits panel on this page before installing. The skill itself contains only documentation, decision trees, and reference guides with no executable code.

Frontend Developmentfrontendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.