Recipe context
styled() plus a provider. Build a compound component around a single-element recipe.
createRecipeContext is styled() plus a provider. A parent sets variant props
once. Every withContext child picks them up. That is how you build a compound component around a single-element
recipe.
You need jsxFramework in your config, or styled-system/jsx is not
generated. This helper is a Client Component ("use client").
It works with cva, a config recipe, or an inline recipe definition.
import { cva } from '../styled-system/css'
import { createRecipeContext } from '../styled-system/jsx'
const buttonRecipe = cva({
base: { display: 'inline-flex' },
variants: {
size: {
sm: { px: '3' },
lg: { px: '5' }
}
}
})
const { withContext, PropsProvider } = createRecipeContext(buttonRecipe)
export const Button = withContext('button')<Button size="lg" type="submit" onClick={onSave}>
Save
</Button>size is the variant. type and onClick go to the <button>.
A config recipe is the same call with a different import:
import { button } from '../styled-system/recipes'
import { createRecipeContext } from '../styled-system/jsx'
const { withContext, PropsProvider } = createRecipeContext(button)withContext
withContext(
component, // 'button' or Link
options? // same as styled()
)options is the same object as styled().
Wrapping an element
One recipe can back more than one component. Button is a button. LinkButton is an a. Same variants.
export const Button = withContext('button')
export const LinkButton = withContext('a')<Button size="lg">Save</Button>
<LinkButton size="lg" href="/docs">
Docs
</LinkButton>For a config recipe, list both tags. See Config recipe.
Wrapping an external component
Pass the component you don't own.
import { ark } from '@ark-ui/react/factory'
import Link from 'next/link'
export const Button = withContext(ark.button, {
defaultProps: { type: 'button' }
})
export const LinkButton = withContext(Link)Default props
export const Button = withContext('button', {
defaultProps: {
type: 'button',
size: 'sm'
}
})Incoming props win over defaults, so <Button size="lg" /> still works.
unstyled
Skip the recipe classes. Style props, css, and className still apply. Same idea as
styled().
<Button unstyled css={{ bg: 'red.500' }}>
Click me
</Button>PropsProvider
The PropsProvider lets you style a set of buttons, tags, or avatars as one group. Export it as ButtonGroup,
TagGroup, or AvatarGroup. The group sets variant props once. Every withContext child picks them up.
export const Button = withContext('button')
export const ButtonGroup = PropsProvider<ButtonGroup size="lg" visual="outline">
<Button>Save</Button>
<Button visual="solid">Cancel</Button>
</ButtonGroup>Save inherits size="lg" and visual="outline". Cancel keeps the size and overrides visual. Incoming props
win over the group.
Adding layout
When the group also lays out its children, split recipe props off and put style props on HStack. Use spaceX, not
gap, when the overlap needs to be negative.
import { HStack } from '../styled-system/jsx'
const { withContext, PropsProvider } = createRecipeContext(avatar)
export const Avatar = withContext('div')
export function AvatarGroup(props) {
const [variantProps, restProps] = avatar.splitVariantProps(props)
return (
<PropsProvider {...variantProps}>
<HStack spaceX="-2" {...restProps} />
</PropsProvider>
)
}<AvatarGroup size="lg" px="4">
<Avatar />
<Avatar />
</AvatarGroup>size styles the avatars. px styles the stack.
Config recipe
Extract sees size on ButtonGroup, not on Button. A renamed export like LinkButton is the same miss. List every
tag that carries variant props, or the CSS is missing:
export const buttonRecipe = defineRecipe({
className: 'button',
jsx: ['Button', 'ButtonGroup', 'LinkButton']
})
export const LinkButton = withContext('a')
export const ButtonGroup = PropsProvidercva already emits every variant, so the extra names do not matter there. The full tag rule is on
Tracking JSX.