Concepts

JSX Style Context

JSX Style Context is a powerful feature that allows you to create compound components from slot recipes.

It uses a context-based approach to distribute recipe styles across multiple child components, making it easier to style headless UI libraries like Ark UI, and Radix UI.

Atomic Slot Recipe

  • Create a slot recipe using the sva function
  • Pass the slot recipe to the createStyleContext function
  • Use the withProvider and withContext functions to create compound components
// components/ui/card.tsx
 
import { sva } from 'styled-system/css'
import { createStyleContext } from 'styled-system/jsx'
 
const card = sva({
  slots: ['root', 'label'],
  base: {
    root: {},
    label: {}
  },
  variants: {
    size: {
      sm: { root: {} },
      md: { root: {} }
    }
  },
  defaultVariants: {
    size: 'sm'
  }
})
 
const { withProvider, withContext } = createStyleContext(card)
 
const Root = withProvider('div', 'root')
const Label = withContext('label', 'label')
 
export const Card = {
  Root,
  Label
}

Then you can use the Root and Label components to create a card.

// app/page.tsx
 
import { Card } from './components/ui/card'
 
export default function App() {
  return (
    <Card.Root>
      <Card.Label>Hello</Card.Label>
    </Card.Root>
  )
}

Config Slot Recipe

The createStyleContext function can also be used with slot recipes defined in the panda.config.ts file.

  • Pass the config recipe to the createStyleContext function
  • Use the withProvider and withContext functions to create compound components
// components/ui/card.tsx
 
import { card } from '../styled-system/recipes'
import { createStyleContext } from 'styled-system/jsx'
 
const { withProvider, withContext } = createStyleContext(card)
 
const Root = withProvider('div', 'root')
const Label = withContext('label', 'label')
 
export const Card = {
  Root,
  Label
}

Then you can use the Root and Label components to create a card.

// app/page.tsx
 
import { Card } from './components/ui/card'
 
export default function App() {
  return (
    <Card.Root>
      <Card.Label>Hello</Card.Label>
    </Card.Root>
  )
}

createStyleContext

This function is a factory function that returns three functions: withRootProvider, withProvider, and withContext.

withRootProvider

Creates the root component that provides the style context. Use this when the root component does not render an underling DOM element.

import { Dialog } from '@ark-ui/react'
 
//...
 
const DialogRoot = withRootProvider(Dialog.Root)

withProvider

Creates a component that both provides context and applies the root slot styles. Use this when the root component renders an underling DOM element.

💡

Note: It requires the root slot parameter to be passed.

import { Avatar } from '@ark-ui/react'
 
//...
 
const AvatarRoot = withProvider(Avatar.Root, 'root')

withContext

Creates a component that consumes the style context and applies slot styles. It does not accept variant props directly, but gets them from context.

import { Avatar } from '@ark-ui/react'
 
//...
 
const AvatarImage = withContext(Avatar.Image, 'image')
const AvatarFallback = withContext(Avatar.Fallback, 'fallback')

unstyled prop

Every component created with createStyleContext supports the unstyled prop to disable styling. It is useful when you want to opt-out of the recipe styles.

  • When applied the root component, will disable all styles
  • When applied to a child component, will disable the styles for that specific slot
// Removes all styles
<AvatarRoot unstyled>
  <AvatarImage />
  <AvatarFallback />
</AvatarRoot>
 
// Removes only the styles for the image slot
<AvatarRoot>
  <AvatarImage unstyled css={{ bg: 'red' }} />
  <AvatarFallback />
</AvatarRoot>

Guides

Config Recipes

The rules of config recipes still applies when using createStyleContext. Ensure the name of the final component matches the name of the recipe.

💡

If you want to use a custom name, you can configure the recipe's jsx property in the panda.config.ts file.

// recipe name is "card"
import { card } from '../styled-system/recipes'
 
const { withRootProvider, withContext } = createStyleContext(card)
 
const Root = withRootProvider('div')
const Header = withContext('header', 'header')
const Body = withContext('body', 'body')
 
// The final component name must be "Card"
export const Card = {
  Root,
  Header,
  Body
}

Default Props

Use defaultProps option to provide default props to the component.

const { withContext } = createStyleContext(card)
 
export const CardHeader = withContext('header', 'header', {
  defaultProps: {
    role: 'banner'
  }
})