Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
edeaa2f
add FAQ and other sections
LFDanLu Oct 23, 2025
8a1f97e
add section for creating custom components
LFDanLu Oct 23, 2025
28eb788
initial stab at extracting types from style macro
LFDanLu Oct 24, 2025
457c59a
testing sizing properties
LFDanLu Oct 24, 2025
a15c24f
Merge branch 'main' of github.com:adobe/react-spectrum into reference…
LFDanLu Oct 24, 2025
819c076
revert export of theme object since it was breaking inference
LFDanLu Oct 24, 2025
3b991ea
adding more values
LFDanLu Oct 28, 2025
42b0406
reorg
LFDanLu Oct 28, 2025
36f1a6f
add the rest of the properties and double check them
LFDanLu Oct 28, 2025
e2b29c9
add more type links
LFDanLu Oct 28, 2025
4c94cd0
add mdn links for common types and specific css properties
LFDanLu Oct 29, 2025
83262ef
add relative links so basecolors and various dimension strings link b…
LFDanLu Oct 29, 2025
5d2b172
add descriptions to table and get rid of unessasary isRelative
LFDanLu Oct 30, 2025
980d3d6
add short hand and condition tables
LFDanLu Oct 30, 2025
59e9400
add subpage for reference and advanced style macro section
LFDanLu Oct 30, 2025
a414886
support subpage list under the ToC
LFDanLu Oct 31, 2025
0b2751d
forgot to move last bit of content
LFDanLu Oct 31, 2025
4ef4996
update FAQ, add mobile related pages, link from RAC styling
LFDanLu Oct 31, 2025
b04b7af
Merge branch 'main' of github.com:adobe/react-spectrum into style_mac…
LFDanLu Oct 31, 2025
807beb0
fix mobile styling
LFDanLu Oct 31, 2025
0c6ac3f
review comments
LFDanLu Nov 3, 2025
a3d57a9
fix build links?
LFDanLu Nov 3, 2025
30bab22
rename mdx file to fix all links
LFDanLu Nov 3, 2025
44b0d8d
Merge branch 'main' of github.com:adobe/react-spectrum into style_mac…
LFDanLu Nov 3, 2025
5084b6b
small fixes
LFDanLu Nov 3, 2025
34ad8cc
move reference out of subpage and merge styling page
LFDanLu Nov 3, 2025
6bf1724
make icons page lowercase and fix shorthands link
LFDanLu Nov 4, 2025
6e508f2
whoops forgot to make git track the capitalization rename
LFDanLu Nov 4, 2025
4b38f98
addressing feedback from quarry team
LFDanLu Nov 4, 2025
2e13868
Merge branch 'main' of github.com:adobe/react-spectrum into style_mac…
LFDanLu Nov 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/@react-spectrum/s2/style/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const iconSizes = {

export function iconStyle(this: MacroContext | void, options: IconStyle): StyleString<Exclude<keyof IconStyle, 'color' | 'size'>> {
let {size = 'M', color, ...styles} = options;

if (color) {
styles['--iconPrimary'] = {
type: 'fill',
Expand Down
19 changes: 19 additions & 0 deletions packages/dev/s2-docs/pages/react-aria/styling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,25 @@ With this configured, all states for React Aria Components can be accessed with
</ListBoxItem>
```

## Style macro

If you want to build custom components that follow Spectrum design tokens and styling, you can use the [style macro](../s2/styling.html) from React Spectrum. The `style` macro is a build-time CSS generator that provides type safe access to Spectrum 2 design tokens including colors, spacing, sizing, and typography.

```tsx
import {Checkbox} from 'react-aria-components';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};

<Checkbox
className={style({
backgroundColor: {
default: 'gray-100',
isHovered: 'gray-200',
isSelected: 'gray-900'
}
})}
/>
```

## Animation

React Aria Components supports both [CSS transitions](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_transitions/Using_CSS_transitions) and [keyframe animations](https://developer.mozilla.org/en-US/docs/Web/CSS/@keyframes), and works with JavaScript animation libraries like [Framer Motion](https://www.framer.com/motion/).
Expand Down
80 changes: 80 additions & 0 deletions packages/dev/s2-docs/pages/s2/reference.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {Layout} from '../../src/Layout';
import {InlineAlert, Heading, Content, Link} from '@react-spectrum/s2';
import {S2Colors} from '../../src/S2Colors';
import {S2Typography} from '../../src/S2Typography';
import {StyleMacroProperties} from '../../src/types';
import {getPropertyDefinitions, getShorthandDefinitions} from '../../src/styleProperties';
export default Layout;

export const section = 'Components';
export const tags = ['style', 'macro', 'spectrum', 'custom', 'values', 'reference'];
export const description = 'Reference table for the style macro';

# Style Macro

The `style` macro supports a constrained set of values per property that conform to Spectrum 2.

## Colors

All Spectrum 2 color tokens are available across color properties (e.g., `backgroundColor`, `color`, `borderColor`).

<S2Colors />

<StyleMacroProperties properties={getPropertyDefinitions('color')} />
Comment on lines +22 to +24
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, we should add some margin between these:

Image


## Dimensions

Spacing props like `margin` and `padding` accept values on a **4px grid**. These are specified in `px` and get converted to `rem`. In addition to numbers, these named options are available:

- `edge-to-text` – default spacing between the edge of a control and its text. Relative to control height.
- `pill` – default spacing between the edge of a pill-shaped control and its text. Relative to control height.
- `text-to-control` – default spacing between text and a control (e.g., label and input). Scales with font size.
- `text-to-visual` – default spacing between text and a visual element (e.g., icon). Scales with font size.

Size props like `width` and `height` accept arbitrary pixel values. Values are converted to `rem` and multiplied by 1.25x on touch devices to increase hit targets.

<StyleMacroProperties properties={getPropertyDefinitions('dimensions')} />

## Text

Spectrum 2 typography can be applied via the `font` [shorthand](#shorthand), which sets `fontFamily`, `fontSize`, `fontWeight`, `lineHeight`, and `color`. You can override any of these individually.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't this repeat the content under Typography for the most part?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats right, the content here is the same. Open to opinions if it should stay like that, I felt like some people might jump to this page on its own since it is a reference and therefore it would be useful to have the additional context/explanation


```tsx
<main>
<h1 className={style({font: 'heading-xl'})}>Heading</h1>
<p className={style({font: 'body'})}>Body</p>
<ul className={style({font: 'body-sm', fontWeight: 'bold'})}>
<li>List item</li>
</ul>
</main>
```

Type scales include: UI, Body, Heading, Title, Detail, and Code. Each scale has a default and additional t-shirt sizes (e.g., `ui-sm`, `heading-2xl`, `code-xl`).

<S2Typography />


<StyleMacroProperties properties={getPropertyDefinitions('text')} />


## Effects

<StyleMacroProperties properties={getPropertyDefinitions('effects')} />

## Layout

<StyleMacroProperties properties={getPropertyDefinitions('layout')} />

## Misc

<StyleMacroProperties properties={getPropertyDefinitions('misc')} />

## Shorthands

Shorthands apply their provided value to commonly grouped properties.

<StyleMacroProperties properties={getShorthandDefinitions('shorthand')} />

## Conditions

<StyleMacroProperties properties={getPropertyDefinitions('conditions')} />
174 changes: 93 additions & 81 deletions packages/dev/s2-docs/pages/s2/styling.mdx
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For those reviewing this page, compare the current experience with the old: https://reactspectrum.blob.core.windows.net/reactspectrum/5084b6bfa7066fbe90e520c9575422bf0df27dd0/s2-docs/s2/styling.html

In particular the organization/existence of subpage/contents, open to opinions as to which is preferred

Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {Layout} from '../../src/Layout';
import {InlineAlert, Heading, Content, Link} from '@react-spectrum/s2';
import {S2Colors} from '../../src/S2Colors';
import {S2Typography} from '../../src/S2Typography';
import {S2StyleProperties} from '../../src/S2StyleProperties';
import {S2FAQ} from '../../src/S2FAQ';
export default Layout;

export const section = 'Guides';
Expand All @@ -11,7 +10,7 @@ export const description = 'Styling in React Spectrum';

# Styling

React Spectrum includes a build-time style macro that generates atomic CSS and lets you apply Spectrum tokens directly in your components with type-safe autocompletion.
React Spectrum includes a build-time `style` macro that generates atomic CSS and lets you apply Spectrum tokens directly in your components with type-safe autocompletion.

## Style macro

Expand All @@ -37,6 +36,14 @@ Colocating styles with your component code means:
- Develop more efficiently – no switching files or writing selectors.
- Refactor with confidence – changes are isolated; deleting a component removes its styles.

<InlineAlert variant="informative">
<Heading>Important Note</Heading>
<Content>
Due to the atomic nature of the generated CSS rules, it is strongly recommended that you follow the best practices listed [below](#css-optimization).
Failure to do so can result in large number of duplicate rules and obtuse styling bugs.
</Content>
</InlineAlert>
Comment on lines 41 to 47
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe overkill, but it felt like it was important to mention this up front so people realize that this is important


## Spectrum components

The `styles` prop accepts a limited set of CSS properties, including layout, spacing, sizing, and positioning. Other styles such as colors and internal padding cannot be customized within Spectrum components.
Expand Down Expand Up @@ -86,95 +93,26 @@ import {Button} from '@react-spectrum/s2';
'visibility'
]} />

### UNSAFE Style Overrides

We highly discourage overriding the styles of React Spectrum components because it may break at any time when we change our implementation, making it difficult for you to update in the future. Consider using [React Aria Components](https://react-spectrum.adobe.com/react-aria/) with our style macro to build a custom component with Spectrum styles instead.

With that being said, the `UNSAFE_className` and `UNSAFE_style` props are supported on Spectrum 2 components as last-resort escape hatches.

```tsx
/* YourComponent.tsx */
import {Button} from '@react-spectrum/s2';
import './YourComponent.css';

function YourComponent() {
return <Button UNSAFE_className="your-unsafe-class">Button</Button>;
}
```

```css
/* YourComponent.css */
.your-unsafe-class {
background: red;
}
```

## Values

The `style` macro supports a constrained set of values per property that conform to Spectrum 2. This improves consistency and maintainability.

### Colors

All Spectrum 2 color tokens are available across color properties (e.g., `backgroundColor`, `color`, `borderColor`).

<S2Colors />

### Spacing

Spacing props like `margin` and `padding` accept values on a **4px grid**. These are specified in `px` and get converted to `rem`. In addition to numbers, these named options are available:

- `edge-to-text` – default spacing between the edge of a control and its text. Relative to control height.
- `pill` – default spacing between the edge of a pill-shaped control and its text. Relative to control height.
- `text-to-control` – default spacing between text and a control (e.g., label and input). Scales with font size.
- `text-to-visual` – default spacing between text and a visual element (e.g., icon). Scales with font size.

### Sizing

Size props like `width` and `height` accept arbitrary pixel values. Values are converted to `rem` and multiplied by 1.25x on touch devices to increase hit targets.

### Typography

Spectrum 2 typography is applied via the `font` shorthand, which sets `fontFamily`, `fontSize`, `fontWeight`, `lineHeight`, and `color`. You can override any of these individually.

```tsx
<main>
<h1 className={style({font: 'heading-xl'})}>Heading</h1>
<p className={style({font: 'body'})}>Body</p>
<ul className={style({font: 'body-sm', fontWeight: 'bold'})}>
<li>List item</li>
</ul>
</main>
```

Type scales include: UI, Body, Heading, Title, Detail, and Code. Each scale has a default and additional t-shirt sizes (e.g., `ui-sm`, `heading-2xl`, `code-xl`).

<S2Typography />

<InlineAlert variant="notice">
<Heading>Important Note</Heading>
<Content>
Only use `<Heading>` and `<Text>` inside Spectrum components with predefined styles (e.g., `<Dialog>`, `<MenuItem>`). They are unstyled by default and should not be used standalone. Use HTML elements with the style macro instead.
</Content>
</InlineAlert>

## Conditional styles

Define conditional values as objects to handle media queries, UI states (hover/press), and variants. This keeps all values for a property together.
Note how the example below uses a predefined [breakpoint](./reference.html#conditions) alongside a custom media query.

```tsx
<div
className={style({
padding: {
default: 8,
lg: 32
lg: 32,
'@media (min-width: 2560px)': 64,
}
})}
/>
```

Conditions are mutually exclusive and ordered. The macro uses CSS cascade layers so the last matching condition wins without specificity issues.

### Runtime conditions
## Runtime conditions

When runtime conditions are detected (e.g., variants, UI states), the macro returns a function to resolve styles at runtime.

Expand All @@ -195,13 +133,14 @@ function MyComponent({variant}: {variant: 'primary' | 'secondary'}) {
}
```

Boolean conditions starting with `is` can be used directly without nesting:
Boolean conditions starting with `is` or `allows` can be used directly without nesting:

```tsx
const styles = style({
backgroundColor: {
default: 'gray-100',
isSelected: 'gray-900'
isSelected: 'gray-900',
allowsRemoving: 'gray-400'
}
});

Expand All @@ -222,7 +161,7 @@ import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
isSelected: 'gray-900'
}
})}
/>
/>
```

### Nesting conditions
Expand Down Expand Up @@ -253,6 +192,11 @@ const styles = style({
Extract common styles into constants and spread them into `style` calls. These must be in the same file or imported from another file as a macro.

```tsx
// style-utils.ts
export const bannerBackground = () => 'blue-1000' as const;

// component.tsx
import {bannerBackground} from './style-utils' with {type: 'macro'};
const horizontalStack = {
display: 'flex',
alignItems: 'center',
Expand All @@ -261,6 +205,7 @@ const horizontalStack = {

const styles = style({
...horizontalStack,
backgroundColor: bannerBackground(),
columnGap: 4
});
```
Expand Down Expand Up @@ -308,9 +253,58 @@ const buttonStyle = style({
<Button styles={buttonStyle}>Press me</Button>
```

## Setting CSS variables

CSS variables can be directly defined in a `style` macro, allowing child elements to then access them in their own styles.
A `type` should be provided to specify the CSS property type the `value` represents.

```tsx
const parentStyle = style({
'--rowBackgroundColor': {
type: 'backgroundColor',
value: 'gray-400'
}
});

const childStyle = style({
backgroundColor: '--rowBackgroundColor'
});
```

## Creating custom components

In-depth examples are coming soon!

`mergeStyles` can be used to merge the style strings from multiple macros together, with the latter styles taking precedence similar to object spreading.
This behavior can be leveraged to create a component API that allows an end user to only override specific styles conditionally.
Comment on lines +281 to +286
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed previously that for this first pass that we want to stick with just modifying this page and create examples of style macros with RAC after the release of the docs. I've opted to keep this section and the once above it for now just to let people know about the existence of mergeStyles/iconStyle/and setting CSS variables with macros, but ideally these would move into an "Advanced" page or something similar to cut down on length here. Will comment on the file as to what I'm thinking the final division should look like

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we export mergeStyles yet? I don't see it in our exports?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not yet, but there was mention of doing so a while back in the beta slack channel. I've put it in the S2 1.0 canvas so we can track that


```tsx
// User can override the component's background color ONLY if it isn't "quiet"
const baselineStyles = style({backgroundColor: 'gray-100'}, ['backgroundColor']);
const quietStyles = style({backgroundColor: 'transparent'});
const userStyles = style({backgroundColor: 'celery-600'});

function MyComponent({isQuiet, styles}: {isQuiet?: boolean, styles?: StyleString}) {
let result = mergeStyles(
baselineStyles(null, styles),
isQuiet ? quietStyles : null
);

return <div className={result}>My component</div>
}

// Displays quiet styles
<MyComponent isQuiet styles={userStyles} />

// Displays user overrides
<MyComponent styles={userStyles} />
```

The `iconStyle` macro should be used when styling Icons, see the [docs](./Icons.html#iconstyle) for more information.

## CSS optimization
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optimization sounds so optional

maybe it'd be better to structure all bundler setups into their own page which includes how the optimization setup works, but then it all sounds much more mandatory

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

happy for the team to weigh in here then, especially since it would fall within the changes made in #9126


The style macro relies on CSS bundling and minification for optimal output. Follow these best practices:
The `style` macro relies on CSS bundling and minification for optimal output. Follow these best practices:

- Ensure styles are extracted into a CSS bundle; do not inject at runtime with `<style>` tags.
- Use a CSS minifier like `lightningcss` to deduplicate common rules (consider in dev for easier debugging).
Expand Down Expand Up @@ -362,4 +356,22 @@ CSS resets are strongly discouraged. Global CSS selectors can unintentionally af
/* App.css */
@layer reset, _;
@import "reset.css" layer(reset);
```
```

## Developing with style macros

Since `style` macros are quite different from using `className`/`style` directly, many may find it initially challenging to debug and develop against.
Below are some useful tools that may benefit your developer experience:

- The [atomic-css-devtools](https://github.com/astahmer/atomic-css-devtools) extension presents an inspected element's atomic CSS rules
in a non-atomic format, making it easier to scan.
Comment on lines +375 to +376
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: replace with the new devtool extension @snowystinger is making


- This [sandbox](https://codesandbox.io/p/devbox/react-spectrum-s2-style-macro-template-h6fpsq) is preconfigured to support React Spectrum S2, React Aria Components, and
the `style` macros for quick prototyping.

- If you are using Cursor, we offer a set of [Cursor rules](https://github.com/adobe/react-spectrum/blob/main/rules/style-macro.mdc) to use when developing with style macros. Additionally,
we have MCP servers for [React Aria](../react-aria/mcp.html) and [React Spectrum](./mcp.html) respectively that interface with the docs.

## FAQ
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we make use of disclosures here? (but maybe a follow-up if it takes too long to style and what not)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats a good suggestion, I was a bit torn on if the current style was better or now (inspired by our own FAQ in github) but I've definitely seen both styles on other sites like MUI and Untitled UI. I'll bring this up with the rest of the team and see what people like


<S2FAQ />
Loading