concepts
merging styles

Merging Styles

Learn how to merge multiple styles without conflicts.

Merging css objects

You can merge multiple style objects together using the css function.

import { css } from 'styled-system/css'
 
const style1 = {
  bg: 'red',
  color: 'white',
}
 
const style2 = {
  bg: 'blue',
}
 
const className = css(style1, style2) // => 'bg_blue c_white'

In some cases though, the style object might not be colocated in the same file as the component. In this case, you can use the css.raw function to preserve the original style object.

💡

css.raw(...) and a config recipe's .raw(...) are identity functions: they return their input, and serve as a hint to the compiler that the value is a style object. cva(...).raw(...) and sva(...).raw(...) are not — they resolve the variants and return the merged style object, which is what makes the cva example below work.

style.js

import { css } from 'styled-system/css'
 
export const style1 = css.raw({
  bg: 'red',
  color: 'white',
})
 
// component.js
import { css } from 'styled-system/css'
import { style1 } from './style.js'
 
const style2 = css.raw({
  bg: 'blue',
})
 
const className = css(style1, style2) // => 'bg_blue c_white'

Spreading css.raw objects

💡

Added in v1.6.1

You can also spread css.raw objects within style declarations. This is particularly useful for reusing styles in nested selectors, conditions, and complex compositions:

Child selectors

import { css } from 'styled-system/css'
 
const baseStyles = css.raw({ margin: 0, padding: 0 })
 
const component = css({
  '& p': { ...baseStyles, fontSize: '1rem' },
  '& h1': { ...baseStyles, fontSize: '2rem' },
})

Nested conditions

import { css } from 'styled-system/css'
 
const interactive = css.raw({ cursor: 'pointer', transition: 'all 0.2s' })
 
const card = css({
  _hover: {
    ...interactive,
    _dark: { ...interactive, color: 'white' },
  },
})

Merging cva + css styles

The same technique can be used to merge an inline cva recipe and a style object.

import { css, cx, cva } from 'styled-system/css'
 
const overrideStyles = css.raw({
  bg: 'red',
  color: 'white',
})
 
const buttonStyles = cva({
  base: {
    bg: 'blue',
    border: '1px solid black',
  },
  variants: {
    size: {
      small: { fontSize: '12px' },
    },
  },
})
 
const className = css(
  // returns the resolved style object
  buttonStyles.raw({ size: 'small' }),
  // add the override styles
  overrideStyles,
)
 
// => 'bg_red bd_1px_solid_black c_white fs_12px'

Merging sva + css styles

The same technique can be used to merge an inline sva recipe and a style object.

import { css, sva } from 'styled-system/css'
 
const overrideStyles = css.raw({
  bg: 'red',
  color: 'white',
})
 
const buttonStyles = sva({
  slots: ['root'],
  base: {
    root: {
      bg: 'blue',
      border: '1px solid black',
    },
  },
  variants: {
    size: {
      small: {
        root: { fontSize: '12px' },
      },
    },
  },
})
 
// returns the resolved style object for all slots
const { root } = buttonStyles.raw({ size: 'small' })
 
const className = css(
  root,
  // add the override styles
  overrideStyles,
)
 
// => 'bg_red bd_1px_solid_black c_white fs_12px'

Merging config recipe and style object

With the Vite compiler, an analyzable cx() composes the recipe and utility StyleSets before allocating shared atoms. Later arguments win, and losing declarations are not emitted for that composition.

import { css, cx } from 'styled-system/css'
import { button } from 'styled-system/recipes'
 
const className = cx(
  // resolves the selected recipe declarations
  button({ size: 'small' }),
  // add the override styles
  css({ bg: 'red' }), // => 'bg_red'
)
 
// => a shared atom string containing the final red background

That override works because Vite can see and compose both Bamboo StyleSets. If one argument is an arbitrary runtime class, cx remains a string join and makes no conflict guarantee — see Classname concatenation.

Merging within JSX component

Use statically available objects for composition. For component choices that vary at runtime, expose a finite recipe variant instead of accepting an arbitrary style object:

import { cva } from '../styled-system/css'
 
const card = cva({
  base: { color: 'white' },
  variants: {
    tone: {
      red: { bg: 'red.500' },
      blue: { bg: 'blue.500' },
    },
  },
  defaultVariants: { tone: 'red' },
})
 
function Card({ title, tone }: { title: string; tone?: 'red' | 'blue' }) {
  return (
    <div className={card({ tone })}>
      <h1>{title}</h1>
    </div>
  )
}
 
function Demo() {
  return <Card title="Hello World" tone="blue" />
}

css(base, props.css) with an open runtime prop cannot be compiled. Renaming the prop or wrapping its value in css.raw() does not change that requirement. For values such as a dragged offset or user-selected color, use an inline style or a CSS variable as described in dynamic styling.