toned-styles
Overview
Getting StartedCore Concepts
API Reference
defineSystemstylesheetvariantsuseStylesMedia Queries
Guides
React WebReact NativeThemingInteractive StylesSSR & SSG

variants

The .variants() method chains onto a stylesheet to add conditional styling based on component state. Variant types are declared via a generic parameter, giving you full type safety from definition through to consumption.

Signature

const styles = stylesheet({ ... }).variants<VariantMap>(($) => ({
  [$.variantName('value')]: {
    elementName: { /* token overrides */ },
  },
}))

Defining Variants

Pass a type parameter describing the variant keys and their allowed values. Required variants must be provided by the consumer; optional variants (marked with ?) are applied only when present:

const buttonStyles = stylesheet({
  container: {
    borderRadius: 'medium',
    borderWidth: 'none',
    cursor: 'pointer',
  },
  label: {},
}).variants<{
  size: 'm' | 's'            // required
  variant: 'accent' | 'danger' // required
  alignment?: 'icon-only' | 'icon-left' | 'icon-right' // optional
}>(($) => ({
  [$.variant('accent')]: {
    container: { bgColor: 'action' },
    label: { textColor: 'on_action' },
  },
  [$.variant('danger')]: {
    container: { bgColor: 'status_error' },
    label: { textColor: 'on_status_error' },
  },
  [$.size('m')]: {
    container: { paddingX: 3 },
  },
  [$.size('s')]: {
    container: { paddingX: 2, paddingY: 1 },
  },
}))

The Dollar-Sign Builder

The callback receives a $ builder object with a method for each variant key. Calling $.size('m') produces a computed key that the runtime uses to match against the state passed to useStyles.

Compound Variants

Chain multiple variant calls to create compound conditions that only match when all specified variants are active simultaneously:

($ => ({
  [$.size('m').alignment('icon-only')]: {
    container: { paddingX: 2, paddingY: 2 },
  },
  [$.size('s').alignment('icon-only')]: {
    container: { paddingX: 1, paddingY: 2 },
  },
}))

Compound variants are evaluated after individual variant rules, so they naturally override conflicting properties.

Pseudo-state Variants

You can target pseudo-states like hover by using the 'element:pseudo' key syntax inside a variant block:

[$.variant('accent')]: {
  container: { bgColor: 'action' },
  label: { textColor: 'on_action' },

  'container:hover': {
    container: { bgColor: 'action_secondary' },
    label: { textColor: 'on_action_secondary' },
  },
}

Responsive Variants

Breakpoints work inside variant blocks, so a variant can define responsive overrides for its elements:

[$.layout('grid')]: {
  container: {
    style: { display: 'grid', gridTemplateColumns: '1fr' },
    '@md': {
      style: { gridTemplateColumns: '1fr 1fr' },
    },
    '@lg': {
      style: { gridTemplateColumns: '1fr 1fr 1fr' },
    },
  },
}

Named Styles ($composes)

When multiple variants share common element overrides, you can extract them into a named style and compose them into variants. This avoids duplicating the same token values across variant rules.

Defining Named Styles

Use $('name') to define a named style block. Named styles are not variant rules — they are reusable fragments that can be composed into actual variants:

const buttonStyles = stylesheet({
  container: { borderRadius: 'medium' },
  label: {},
}).variants<{ size: 'm' | 's'; variant: 'accent' | 'danger' }>(($) => ({
  // Named style — shared across variants
  $('interactive'): {
    'container:hover': {
      container: { shadow: 'medium' },
    },
  },

  [$.variant('accent')]: {
    $compose: 'interactive',
    container: { bgColor: 'action' },
    label: { textColor: 'on_action' },
  },

  [$.variant('danger')]: {
    $compose: 'interactive',
    container: { bgColor: 'status_error' },
    label: { textColor: 'on_status_error' },
  },
}))

Both accent and danger inherit the hover shadow from interactive, without repeating it.

Element-Level $compose

Inside a single variant rule, you can compose one element from another element defined in the same block. This is useful when several elements within a variant share a base set of tokens:

[$.size('s')]: {
  base: { paddingX: 2, bgColor: 'muted' },
  container: {
    $compose: 'base',
    borderRadius: 'medium',
  },
  sidebar: {
    $compose: 'base',
    borderRadius: 'small',
  },
}

Here both container and sidebar inherit paddingX and bgColor from base, then add their own overrides. The composed element (base) is not included in the final output — it only serves as a source for composition.

Composing Multiple Sources

Pass an array to $compose to merge from multiple named styles. They are applied in order, and the variant's own properties always take priority:

$('borders'): {
  container: { borderWidth: 'thin', borderColor: 'subtle' },
},
$('spacing'): {
  container: { paddingX: 3, paddingY: 2 },
},

[$.size('m')]: {
  $compose: ['borders', 'spacing'],
  container: { bgColor: 'elevated' },  // own props override composed ones
}

Consuming Variants

Pass variant values as the second argument to useStyles:

function Button({ label, size, variant }: Props) {
  const s = useStyles(buttonStyles, { size, variant })
  return (
    <button {...s.container}>
      <span {...s.label}>{label}</span>
    </button>
  )
}