migration
theme ui

Migrating from Theme UI

Migrate your project from Theme UI to Bamboo.

This guide outlines the steps needed to migrate your project from Theme UI to Bamboo and highlights key design differences between the two libraries.

Here are some similarities between the two libraries.

  • Both support design tokens and themes.
  • Both ship layout primitives — Theme UI as components (Box, Flex, Grid), Bamboo as pattern functions that return a class name.

Below are some of the differences between the two libraries. The one that shapes the whole migration: Bamboo has no JSX style props and no sx prop. Every style is a css(), cva() or pattern call whose class name you put on an element.

Performance

Theme UI relies on @emotion/styled to style components. This means that every time you use the sx prop, runtime CSS-in-JS is required to compute the styles in the browser. This can lead to performance issues in larger applications.

Bamboo's Vite compiler converts CSS-in-JS styles to static CSS at build time, avoiding runtime style computation.

Theming

In Theme UI, you need to wrap your application in a ThemeProvider component which is a wrapper around @emotion/react theme context.

import { ThemeProvider } from 'theme-ui'
 
const theme = {
  fonts: {
    body: 'system-ui, sans-serif',
    heading: '"Avenir Next", sans-serif',
  },
  colors: {
    text: '#000',
    background: '#fff',
  },
}
 
export default function App({ Component, pageProps }) {
  return (
    <ThemeProvider theme={theme}>
      <Component {...pageProps} />
    </ThemeProvider>
  )
}

In Bamboo, you don't need to wrap your application in a ThemeProvider component. Instead, you can pass the theme object to the bamboo.config.js file.

The theme object in Bamboo is broken down into multiple parts, tokens and semanticTokens. The theme specification also required passing the tokens as { value: XX }

bamboo.config.js

import { defineConfig } from '@bamboocss/dev'
 
export default defineConfig({
  theme: {
    extend: {
      tokens: {
        fonts: {
          body: { value: 'system-ui, sans-serif' },
          heading: { value: '"Avenir Next", sans-serif' },
        },
        colors: {
          text: { value: '#000' },
          background: { value: '#fff' },
        },
      },
    },
  },
})

The sx prop

In Theme UI, you can use the sx prop to style any component when you add the jsxImportSource pragma to the top of your file.

/** @jsxImportSource theme-ui */
 
export const Demo = (props) => (
  <div
    {...props}
    sx={{
      color: 'white',
      bg: 'primary',
      fontSize: 4,
    }}
  />
)

Bamboo has no sx prop. Write the same object with the css function and pass the class name it returns. Numeric scale steps become named tokens — the default preset's fontSizes run 2xs through 9xl.

import { css } from '../styled-system/css'
 
export const Demo = (props) => (
  <div
    {...props}
    className={css({
      color: 'white',
      bg: 'primary',
      fontSize: 'xl',
    })}
  />
)

Reach for cva instead when the styles need a name and variants.

Variants

In Theme UI, variants are used to create groups of styles based on the theme. It offers variant groups in the theme for several components.

  • Grid maps to theme.grids
  • Button, IconButton maps to theme.buttons
  • NavLink, Link maps to theme.links
  • Input, Select, Textarea maps to theme.forms
  • Heading, Text maps to theme.text

theme.js

export default {
  colors: {
    primary: '#07c',
    secondary: '#30c',
    accent: '#609'
  },
  buttons: {
    primary: {
      color: 'white',
      bg: 'primary'
    },
    secondary: {
      color: 'white',
      bg: 'secondary'
    },
    accent: {
      color: 'white',
      bg: 'accent'
    }
  }
}
 
// Button.js
<button sx={{ variant: 'buttons.primary' }} />

In Bamboo, multi-variant styles are called recipes. Under Vite, inline and configured recipes resolve into the same globally shared declaration atoms as css(). What you choose is where the recipe lives and how its typed API is generated; recipe identity does not affect emitted CSS identity.

Colocated with the component, using the cva function:

import { cva } from '../styled-system/css'
 
const buttonStyles = cva({
  className: 'button',
  base: {
    display: 'inline-flex',
  },
  variants: {
    variant: {
      primary: { color: 'white', bg: 'primary' },
      secondary: { color: 'white', bg: 'secondary' },
      accent: { color: 'white', bg: 'accent' },
    },
  },
})
 
const Demo = () => <button className={buttonStyles({ variant: 'accent' })} />

Or in the theme.recipes property of the bamboo config — 'config recipes' — which shares the recipe across components and projects and generates only the variants the project uses. You then import it from ../styled-system/recipes. See Recipes.

Color Modes

In Theme UI, colors modes can be used to create a user-configurable light and dark mode values that are automatically applied to components depending on color mode.

theme.js

const theme = {
  colors: {
    primary: '#07c',
    modes: {
      dark: {
        primary: '#0cf',
      },
    },
  },
}
 
// Button.js
const Demo = () => <button sx={{ color: 'primary' }} />

In Bamboo, color mode values are defined as semanticTokens in the theme. Semantic tokens change depending on the color mode, so the call site stays an ordinary css({ color: 'primary' }).

bamboo.config.js

import { defineConfig } from '@bamboocss/dev'
 
export default defineConfig({
  theme: {
    extend: {
      semanticTokens: {
        colors: {
          primary: { value: { base: '#07c', _dark: '#0cf' } },
        },
      },
    },
  },
})

Global Styles

Theme UI offers a Global component (that wraps Emotion’s) for adding global CSS with theme-based values.

import { Global } from 'theme-ui'
 
export default (props) => (
  <Global
    styles={{
      button: {
        m: 0,
        bg: 'primary',
        color: 'background',
        border: 0,
      },
    }}
  />
)

In Bamboo, global styles are defined in the top-level global.css property of the bamboo config — not under theme.

bamboo.config.js

import { defineConfig } from '@bamboocss/dev'
 
export default defineConfig({
  global: {
    css: {
      button: {
        m: 0,
        bg: 'primary',
        color: 'background',
        border: 0,
      },
    },
  },
})

Component Styles

Theme UI offers pre-defined layout components like Box, Stack, Grid, Flex

import { Box, Grid } from 'theme-ui'
 
const Demo = () => (
  <Grid width={[128, null, 192]}>
    <Box bg="primary">Box</Box>
    <Box bg="muted">Box</Box>
    <Box bg="primary">Box</Box>
    <Box bg="muted">Box</Box>
  </Grid>
)

In Bamboo, these are called "layout patterns", or "patterns" for short. Each one is a function that returns a class name, so it composes with any element.

import { css } from '../styled-system/css'
import { grid } from '../styled-system/patterns'
 
const Demo = () => (
  <div className={grid({ width: [128, null, 192] })}>
    <div className={css({ bg: 'primary' })}>Box</div>
    <div className={css({ bg: 'muted' })}>Box</div>
  </div>
)