Patterns
Customize a built-in layout pattern, or define your own reusable layout abstraction.
Patterns are layout helpers like stack, flex, and grid, functions that return a style object instead of you
writing one by hand. This page covers customizing the built-in ones and writing your own.
A pattern config takes:
description: shown in the generated JSDoc.properties: the props the pattern accepts, and their type.defaultValues: what a prop is when the caller omits it. Either a plain object, or a function of the other props, for defaults that depend on each other.transform: takes the resolved props (plus a small set of helpers) and returns the style object to emit.jsxName: the name of the generated JSX component. Defaults to the pascal-case of the pattern name.jsx: which JSX tag names count as a usage of this pattern for extraction, an array of strings or regexes. This is a different thing fromjsxName: it's about recognizing usage, not generating a component.jsxElement: the actual DOM element the generated component renders. Defaults to'div'.blocklist: CSS properties the pattern rejects, enforced at the type level. Marked experimental in the type definitions, expect this one to still move.strict: only type the properties you listed, nothing else. Withstrict: true, arbitrary CSS properties won't type-check on this pattern.
Creating a pattern
Say you want a scrollable pattern, a container with a fixed scroll direction and an optional hidden scrollbar:
const config = {
patterns: {
extend: {
scrollable: {
description: 'A container that allows for scrolling',
defaultValues: {
direction: 'vertical',
hideScrollbar: true
},
properties: {
direction: { type: 'enum', value: ['horizontal', 'vertical'] },
hideScrollbar: { type: 'boolean' }
},
blocklist: ['overflow'], // reject `overflow` at the type level, since this pattern sets it
transform(props) {
const { direction, hideScrollbar, ...rest } = props
return {
overflow: 'auto',
height: direction === 'horizontal' ? '100%' : 'auto',
width: direction === 'vertical' ? '100%' : 'auto',
scrollbarWidth: hideScrollbar ? 'none' : 'auto',
WebkitOverflowScrolling: 'touch',
'&::-webkit-scrollbar': {
display: hideScrollbar ? 'none' : 'auto'
},
...rest
}
}
}
}
}
}transform also receives a second argument with a few helpers (map, isCssUnit, isCssVar, isCssFunction) for
patterns whose props need to branch on what kind of value they got:
transform(props, { isCssVar }) {
// isCssVar(props.gap) is true for something like 'var(--my-gap)'
}Run codegen so the pattern function and its types exist:
import { scrollable } from '../styled-system/patterns'
const App = () => {
return (
<div className={scrollable({ direction: 'vertical', hideScrollbar: true })}>
<div>Scrollable content</div>
</div>
)
}Customizing a built-in pattern
Extend a default pattern the same way, under patterns.extend. Here's
flex narrowed to two directions, with a second component name recognized as a usage of it:
panda.config.ts
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
patterns: {
extend: {
flex: {
properties: {
direction: { type: 'enum', value: ['row', 'column'] },
jsx: ['Flex', 'CustomFlex'] // extraction now also matches <CustomFlex />
}
}
}
}
})Changing a pattern's defaults
defaultValues in patterns.extend changes what a built-in pattern applies when a prop is omitted. Same shape for
every pattern, only the pattern name and keys change.
@pandacss/preset-base sets gap: '8px' on stack. To default to a spacing token
instead:
panda.config.ts
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
patterns: {
extend: {
stack: {
defaultValues: {
gap: '2' // same as <Stack gap="2" />
}
}
}
}
})<Stack /> and stack({}) now default to spacing token 2. A gap passed at the call site still overrides it.
Values can be a token key ('2') or a raw literal ('12px'). stack, hstack, and vstack are separate patterns
with their own defaultValues, so override each one if you want the same default gap everywhere.
Re-run codegen after changing defaults, same as any other config change:
Starting from zero
Everything here assumes you're extending Panda's defaults with patterns.extend. For no built-in patterns at all,
see minimal setup.