Beta Preview

Advanced styling guide

A guide to advanced styling use cases for React Spectrum.

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.

<div
  className={style({
    padding: {
      default: 8,
      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

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

import {style} from '@react-spectrum/s2/style' with {type: 'macro'};

const styles = style({
  backgroundColor: {
    variant: {
      primary: 'accent',
      secondary: 'neutral'
    }
  }
});

function MyComponent({variant}: {variant: 'primary' | 'secondary'}) {
  return <div className={styles({variant})} />
}

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

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

<div className={styles({isSelected: true})} />

Runtime conditions work well with render props in React Aria Components. If you inline styles, you’ll get autocomplete for available conditions.

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'
    }
  })}
/>

Nesting conditions

Nest conditions to apply styles when multiple conditions are true. Conditions at the same level are mutually exclusive; order determines precedence.

const styles = style({
  backgroundColor: {
    default: 'gray-25',
    isSelected: {
      default: 'neutral',
      isEmphasized: 'accent',
      forcedColors: 'Highlight',
      isDisabled: {
        default: 'gray-400',
        forcedColors: 'GrayText'
      }
    }
  }
});

<div className={styles({isSelected, isEmphasized, isDisabled})} />

Reusing styles

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.

// 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',
  columnGap: 8
} as const;

const styles = style({
  ...horizontalStack,
  backgroundColor: bannerBackground(),
  columnGap: 4
});

Create custom utilities by defining your own macros.

// style-utils.ts
export function horizontalStack(gap: number) {
  return {
    display: 'flex',
    alignItems: 'center',
    columnGap: gap
  } as const;
}

Usage:

// component.tsx
import {horizontalStack} from './style-utils' with {type: 'macro'};
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};

const styles = style({
  ...horizontalStack(4),
  backgroundColor: 'base'
});

Built-in utilities

Use focusRing() to add the standard Spectrum focus ring.

import {style, focusRing} from '@react-spectrum/s2/style' with {type: 'macro'};
import {Button} from '@react-spectrum/s2';

const buttonStyle = style({
  ...focusRing(),
  // ...other styles
});

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

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.

// 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 for more information.

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

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

function YourComponent() {
  return <Button UNSAFE_className="your-unsafe-class">Button</Button>;
}
/* YourComponent.css */
.your-unsafe-class {
  background: red;
}