theming
mixins

Mixins

Define reusable named bundles of css declarations.

A mixin is a named bundle of declarations you apply by name. It is the shape you reach for when several elements share a set of properties that is more than one token and less than a component — typography scales, container treatments, animation presets.

Defining mixins

Mixins are defined in the mixins property of the theme.

mixins.ts

import { defineMixins } from '@bamboocss/dev'
 
export const mixins = defineMixins({
  body: {
    description: 'The body text style - used in paragraphs',
    value: {
      fontFamily: 'Inter',
      fontWeight: '500',
      fontSize: '16px',
      lineHeight: '24px',
      letterSpacing: '0',
    },
  },
  container: {
    description: 'container styles',
    value: {
      background: 'gray.50',
      border: '2px solid',
      borderColor: 'gray.500',
    },
  },
  'slide-fade-in': {
    value: {
      transformOrigin: 'var(--transform-origin)',
      animationDuration: 'fast',
      '&[data-placement^=top]': {
        animationName: 'slide-from-top, fade-in',
      },
      '&[data-placement^=bottom]': {
        animationName: 'slide-from-bottom, fade-in',
      },
    },
  },
})
💡

Good to know: The value property maps to a style object that will be applied to the element. It takes the same conditions and nested selectors a css() call does.

Update the config

To use the mixins, we need to update the config object in the bamboo.config.ts file.

bamboo.config.ts

import { defineConfig } from '@bamboocss/dev'
import { mixins } from './mixins'
 
export default defineConfig({
  theme: {
    extend: {
      mixins,
    },
  },
})

This should automatically update the generated theme with the specified mixins. If this doesn't happen, you can run the bamboo codegen command.

Using mixins

Now we can use the mixin property in our components.

import { css } from '../styled-system/css'
 
function App() {
  return (
    <div className={css({ mixin: 'container' })}>
      <p className={css({ mixin: 'body' })}>This is a paragraph from Bamboo with the body mixin.</p>
    </div>
  )
}

Nesting mixins

Mixins support nested structures with a special DEFAULT key. This allows you to create variants of a mixin while having a default fallback.

When you define a DEFAULT key within a nested mixin, you can reference the parent key directly to use the default value.

bamboo.config.ts

export default defineConfig({
  theme: {
    extend: {
      mixins: {
        heading: {
          DEFAULT: {
            value: { fontSize: '1.5rem', lineHeight: '1.2' },
          },
          h1: {
            value: { fontSize: '2.5rem', lineHeight: '1.1' },
          },
        },
      },
    },
  },
})

Now you can use the default heading style or specific variants:

import { css } from '../styled-system/css'
 
function App() {
  return (
    <div>
      <h1 className={css({ mixin: 'heading.h1' })}>Main Title</h1>
      <h2 className={css({ mixin: 'heading' })}>Uses DEFAULT variant</h2>
    </div>
  )
}

Best Practices

Naming conventions

We recommend using the same names your designers use. Common approaches:

  • Sized-based naming (xs, sm, md, lg, xl)
  • Semantic naming that corresponds to html tags in production (caption, paragraph, h1, h2)
  • Descriptive naming that explains the intended use (alert, modal-header, button-label)

Group by what they are for

One flat namespace holds every mixin, so a prefix is what keeps them navigable — text.body, layer.container, motion.slide-fade-in. Nesting gives you this for free, and DEFAULT gives each group a bare name.

This replaces textStyles, layerStyles and animationStyles, and the textStyle, layerStyle and animationStyle properties that applied them. The three ran through one registration and differed only in which css properties the value was allowed to set — a partition that was arbitrary at the edges (color was legal in a text style and a layer style) and costly in the middle: a bundle wanting a font and a border needed two keys and two applications.

Setting a property that does not exist is still an error. Mixin is built on the same property set css() uses rather than on SystemStyleObject, whose index signature would accept a typo — which is what the old allowlists were really protecting, and the reason one of them shipped hypens for as long as it did.