Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
109 changes: 101 additions & 8 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.

My imagined ideal end state for these style macro docs is as follows:

This page aka "Styling"
Contains the following top level sections:

  • Style macro
  • Spectrum components
  • Values
  • CSS optimization
  • CSS Resets
  • Developing with style macros
  • FAQ

An "Advanced" page, maybe named Styling: Advanced? Or maybe a subpage where it is available in the ToC but not the sidebar?
Contains the following content:

  • Conditional styles
  • Reusing styles (maybe should be in Styling)?
  • Setting CSS variables
  • Creating custom components (examples of RAC with style macros. Documentation of size, space, fontRelative? Or do those belong at the Styling level?)

A Reference page
Contains a listing of every value supported by the style macro. Basically what we have for Colors/etc but including a mapping perhaps for things like spacing/sizing/etc

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,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 +37,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 +40 to +46
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 @@ -88,7 +96,7 @@ import {Button} from '@react-spectrum/s2';

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

Expand Down Expand Up @@ -153,7 +161,7 @@ Type scales include: UI, Body, Heading, Title, Detail, and Code. Each scale has
<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.
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>

Expand Down Expand Up @@ -195,13 +203,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 +231,7 @@ import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
isSelected: 'gray-900'
}
})}
/>
/>
```

### Nesting conditions
Expand Down Expand Up @@ -308,9 +317,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 +338 to +343
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


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

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 +420,39 @@ 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 +430 to +431
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](#TODO) and [React Spectrum](https://www.npmjs.com/package/@react-spectrum/mcp) respectively that interface with the docs.

## FAQ

> I'm getting a "Could not statically evaluate macro argument" error.

This indicates that your `style` macro has a condition that isn't evaluable at build time. This can happen for a variety of reasons such
as if you've referenced non-constant variables within your `style` macro or if you've called non-macro functions within your `style` macro.
If you are using Typescript, it can be as simple as forgetting to add `as const` to your own defined reusable macro.
Comment on lines +443 to +445
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 add an example of a macro that would cause this?


> I'm seeing a ton of duplicate rules being generated and/or my dev tools are very slow.

Please make sure you've followed the [best practices listed above](#css-optimization).

> I tried to pass my `style` macro to `UNSAFE_className` but it doesn't work.

The `style` macro is not meant to be used with `UNSAFE_className`. Overrides to the Spectrum styles is highly discouraged,
consider styling an equivalent React Aria Component instead.
Comment on lines +451 to +454
Copy link
Member Author

Choose a reason for hiding this comment

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

Already mentioned in the UNSAFE style overrides section but felt it was worth adding here anyways


> I'm coming from S1, but where are Flex/Grid/etc?

These no longer exist. Please style `<div>`, `<span>`, and other standard HTML elements with the `style` macro instead.
23 changes: 20 additions & 3 deletions packages/dev/s2-docs/src/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ const components = {
p: ({children, ...props}) => <p {...props} className={style({font: {default: 'body', lg: 'body-lg'}, marginY: 24})}>{children}</p>,
ul: (props) => <ul {...props} />,
li: ({children, ...props}) => <li {...props} className={style({font: {default: 'body', lg: 'body-lg'}, marginY: 0})}>{children}</li>,
blockquote: ({children, ...props}) => (
<blockquote
{...props}
className={style({
borderStartWidth: 4,
borderEndWidth: 0,
borderTopWidth: 0,
borderBottomWidth: 0,
borderStyle: 'solid',
borderColor: 'gray-400',
paddingStart: 12,
font: {default: 'body', lg: 'body-lg'},
margin: 'unset'
})}>
{children}
</blockquote>
),
Figure: (props) => <figure {...props} className={style({display: 'flex', flexDirection: 'column', alignItems: 'center', marginY: 32, marginX: 0})} />,
Caption: (props) => <figcaption {...props} className={style({font: 'body-sm'})} />,
CodeBlock: CodeBlock,
Expand All @@ -56,14 +73,14 @@ const getTitle = (currentPage: Page): string => {
if (explicitTitle && explicitTitle !== currentPage.tableOfContents?.[0]?.title && explicitTitle !== currentPage.name) {
return explicitTitle as string;
}

let library = getLibraryLabel(getLibraryFromPage(currentPage));
const pageTitle = currentPage.tableOfContents?.[0]?.title ?? currentPage.name;

if (currentPage.name === 'index.html' || currentPage.name.endsWith('/index.html')) {
return library || 'React Spectrum';
}

return library ? `${pageTitle} | ${library}` : pageTitle;
};

Expand Down