This post will be split in 4 parts:
So, from the endless list of styling solutions and CSS-in-JS
libraries, why should you choose Panda?
Panda CSS uses static analysis
on your code to generate CSS. This means that we look at the code you wrote (source files) and generate CSS from it.
This is a huge advantage over runtime
styling solutions, not only because it’s faster (no need to compute/inject style at runtime, on each render, which could cause style recalculations for the browser), but also because it means that we can generate a raw CSS file that you can use (and cache !) anywhere.
Also sometimes called utility-first
, this is a very popular way of writing CSS.
The idea is to write 1 CSS class per CSS property, and then compose them together. This leads to a small optimized CSS file, since most of the time you’ll reuse the same CSS classes.
@layer utilities { .p_22px { padding: 22px; }
.fs_2xl { font-size: var(--font-sizes-2xl); }
.text_center { text-align: center; }}
Panda CSS leverages TypeScript
to provide type-safety.
This means that you’ll get autocompletion, type-checking and you’ll be yelled at if you do something wrong. ⚠️
This also means that you don’t need a VSCode extension to get autocompletion and type-safety, it’s built-in. (tho, we’ve been working on a VSCode extension that provides super-powered autocompletion and even more features)
import { css } from '../styled-system/css'
function App() { return ( <div className={css({ color: 'blue.300', // will show every possible colors from your theme padding: "2xl" // and this will only show the spacing tokens (shared with margin and other properties) width: '123px', // you can also use raw CSS values
_hover: { color: 'blue.500' }, // and conditions from your config "&:focus": { color: 'green.500' }, // or even arbitrary CSS selectors })} > Hello World </div> )}
ℹ️ Note: By default, config.strictTokens
is set to false
and that means you’ll get autocompletions from your config.theme
but you’ll also be able to use any CSS value.
If you set config.strictTokens
to true
, you’ll only be able to use tokens
that are included in your config
, unless using the escape-hatch syntax [xxx]
:
// using config.strictTokens: trueimport { css } from '../styled-system/css'
function App() { return ( <div className={css({ fontSize: '2xl', // ✅ is a valid token fontSize: '14px', // ❌ TS will throw an error because "14px" is not a token fontSize: '[14px]', // ✅ using escape-hatch syntax for arbitrary values })} > Hello World </div> )}
We provide 2 different presets out of the box (you don’t need to install anything):
@pandacss/preset-base
, which contains the default conditions / utilities / patterns@pandacss/preset-panda
, which contains the default theme (keyframes
/ breakpoints
/ tokens
/ textStyles
)If those presets don’t fit your needs, you can always set config.eject
to true
to remove them and start from scratch with a minimal setup.
You can also create your own presets and share them with the community !
Since Panda is based on Typescript
, you don’t need to install any VSCode extension to get autocompletion (and inline documentation from MDN through csstype
), it’s built-in.
Prettier just works, it’s just functions and objects.
Merging styles ? Covered. Recipes (cva) ? Gotcha.
I mentioned the ability to create your own presets, but of course you can also just customize your panda.config.ts
with your theme (keyframes
/ breakpoints
/ tokens
/ textStyles
) / conditions / utilities / patterns to fit your needs.
Using the extend
keyword inside of any of those config options will allow you to add your own customizations on top of the default presets (and any other that you added).
Everything will be deep merged, that means keys with the same name will be merged together, arrays will be concatenated, etc.
Panda CSS is very flexible and can be used in many different ways. That’s partly because we’re using a codegen step that allows us to generate a style-system
tailored to your needs.
Let’s say you don’t like shorthands
? Set config.shorthands
to false
and they won’t be allowed anymore.
Stricter type-safety ? Set config.strictTokens
to true
Too many properties suggested in the styled
factory and JSX patterns
? Set config.jsxStyleProps
to minimal
(that only allows the css
special property) or none
to disable them completely.
Need to change the @layer
names ? Set config.layers
to whatever you need. (Can be useful to avoid naming conflicts with other styling libraries !)
You can also customize the way we generate CSS, like which separator
should be used in the generated class names, or config.prefix
to add a prefix to all your class names.
We also include a CSS reset by default, but you can disable it with config.preflight
set to false
.
You can see the full list of config options here: https://panda-css.com/docs/references/config
Ever since the rise of CSS-in-JS (thanks to @vjeux !), there’s been a lot of debates about it. Some people love it, some people hate it.
I personally love it, I think it just makes sense to have your whole UI component in one place, markup AND styling. This is basically the idea of colocation taken to the next level.
Well, actually with Panda it’s not really CSS-in-JS
, it’s more like CSS-in-TS
since we’ve got type-safety all over the place.
And that works with most JSX-like frameworks: React / Preact / Svelte / Vue / Solid / Qwik
Panda CSS is compatible with React Server Components (RSC), you don’t need to do anything special, after all we’re just generating CSS at build-time
. 😁
Not only you can use the css
function inside your components file to style them, but you can also use any JSX props that matches either a CSS property name, shorthand
or a utility
defined in your config (or from a preset
).
import { css } from 'styled-system/css'import { styled } from 'styled-system/jsx'
export const App = () => { return ( <div className={css({ display: 'flex', justifyContent: 'center', alignItems: 'center', h: 'full', })} > <styled.button rounded="md" fontWeight="semibold" height="10" px="4" bg={{ base: 'yellow.500', _dark: 'yellow.300' }} color={{ base: 'white', _dark: 'gray.800' }} > Button </styled.button> </div> )}
Related docs : https://panda-css.com/docs/concepts/merging-styles
For example, you can just pass multiple arguments to the css
function, and they’ll be merged together.
import { css } from 'styled-system/css'
css({ display: 'flex', bg: 'blue.500' }, { bg: 'red.500', color: 'white' })// would result in { display: 'flex', bg: 'red.500', color: 'white' }
This can be especially useful with the .raw()
function, available on css
/ recipes
/ patterns
.
This raw
function will return what you passed in (except when used on patterns
, it will transform the object back to a css
-compatible object), and serves as a marker for the static analysis
, to tell the Panda extractor
that you’re creating a style object
and we should generate the CSS for it.
import { css } from 'styled-system/css'
export const base = { display: 'flex', bg: 'blue.500' }// ❌ this will not generate any CSS
export const base = css.raw({ display: 'flex', bg: 'blue.500' })// ✅ this will generate CSS as expected
import { css } from 'styled-system/css'import { base } from './file-1'
export const styles = css.raw(base, { bg: 'red.500', color: 'white' })// ✅ would result in { display: 'flex', bg: 'red.500', color: 'white' }
You can also use the cx
function to merge multiple class names together.
import { css, cx } from 'styled-system/css'const className = cx(css({ display: 'flex', bg: 'blue.500' }), 'my-class')// would result in something like 'd_flex bg_blue.500 my-class'
More examples are available here
Given we want to generate a CSS file at build-time
, we need to know what CSS to generate before running your app, and that’s where static analysis
comes in: by scanning your source files and finding style usage in there.
I would not really say that static analysis
is an intended feature, as it also introduces some limitations, but I feel like it incidentally forces you to write better code, basically it acts like a pit of success.
Since you can’t (easily/directly) use runtime dynamic values
, you’ll have to find a way to make it static, and that’s actually a good thing both in term of code maitainability and performance !
In addition to that benefit, our static extractor is pretty smart, it can detect style usage in many different ways to the point where it doesn’t seem static anymore.
I’m pretty confident it’s one of the most advanced static extractor among build-time
styling solutions !
Runtime conditions, identifier resolving, static evaluation, all of those just work.
import { css } from '../styled-system/css'
const Button = (props) => { const [isSelected, setIsSelected] = useState(false)
return ( <button // ✅ the static extractor will detect both `blue.500` and `yellow.500` // and will generate the CSS for both // ℹ️ you can nest conditions indefinitely and it would still be fine className={css({ color: isSelected ? 'blue.500' : 'yellow.500' })} > {props.children} </button> )}
import { css } from '../styled-system/css'
const colors = { blue: 'blue.500', yellow: 'yellow.500',}const base = { color: colors.blue } // ✅ this will be resolved inside of the `css`const selected = { color: colors['yellow'] } // ✅ this will also be resolved
const Button = (props) => { const isSelected = props.isSelected
return ( <button className={css({ ...base, // ✅ you can mix spread and conditions ...(isSelected ? selected : { fontSize: '12px' }), })} > {props.children} </button> )}
// ✅ the final CSS would successfully include both `color: blue.500`, `color: yellow.500` and `font-size: 12px`
import { css } from '../styled-system/css'
const colorMap = { background: 'red', foreground: 'yellow',}
const colorTints = { background: 400, foreground: '100',}
const getColor = (name: keyof typeof colorMap) => `${colorMap[name]}.${colorTints[name]}`
const Button = (props) => { const isSelected = props.isSelected
return ( <button className={css({ color: getColor('foreground'), // ✅ this will statically evaluate to "yellow.100" })} > {props.children} </button> )}
// ✅ the final CSS would successfully include :// `color: blue.500`, `color: yellow.500`
There are of course still some limitations to static analysis, I wrote a bit about it here
I just said that Panda CSS uses static analysis
to generate CSS, but that doesn’t mean you can’t use runtime dynamic values
! You just have to find a way to make it static.
Related documentation is here
It’s true that you cannot directly use runtime
values (like variables created from useState
) inside Panda’s functions like css
or cva
, since those need statically analyzable arguments.
But you can use CSS variables to achieve the same result, using the style
attribute.
import { css } from '../styled-system/css'
const Button = ({ dynamicColor }) => { return ( <button // ✅ you can create a static relationship // between the CSS variable and the dynamic value className={css({ color: 'var(--color)' })} style={{ '--color': dynamicColor }} > {props.children} </button> )}
You might have seen this kind of prop-based code with other styling solutions:
const Button = styled.button<{ $primary?: boolean }>` font-size: 1em;
/* Adapt the colors based on primary prop */ background: ${(props) => (props.$primary ? '#BF4F74' : 'white')}; color: ${(props) => (props.$primary ? 'white' : '#BF4F74')};`
With Panda CSS, you could use an atomic recipe
(2nd argument of the styled factory
or cva
) to keep the style definition static.
Not only does it achieve the same result but you also unlock the possibilities of having multiple styles bound to the same variant, and of course you can create as many variants as you want.
import { styled } from '../styled-system/jsx'
const Button = styled('button', { base: { fontSize: '1em' }, variants: { visual: { primary: { bg: 'red.200', color: 'white' }, }, },})
Avoid dynamically updating the styles, and instead use CSS selectors to style your components based on their state.
For example, you can use the data-xxx
attributes to store the state, and then use the &[data-xxx]
selector to style it.
import { styled } from '../styled-system/jsx'
const Button = (props) => { const [isSelected, setIsSelected] = useState(false)
return ( <button className={css({ color: isSelected ? 'blue.500' : 'yellow.500' })} data-selected={isSelected ? '' : undefined} className={css({ color: 'yellow.500', '&[data-selected]': { color: 'blue.500' }, })} > {props.children} </button> )}
ℹ️ Note that this specific data-selected
example is built-in with the default conditions, so you could use it like _selected: { color: "blue.500" }
.
Panda analyzes your code to generate CSS, but it also provides a way to generate CSS from rules that you can provide in the config.staticCss
option. This is useful:
https://panda-css.com/docs/guides/static
Panda uses modern CSS features like cascade layers (@layer
), CSS variables, modern selectors like :where
and :is
in generated styles.
Using the CSS @layer
, there’s no specificity war, for example your utilities
style would always prevail on your recipes
style, since they’re always applied in the right order. Read more here.
@layer reset, base, tokens, recipes, utilities;
That being said, if you need to support older browsers you can always use this postcss
plugin to polyfill the @layer
syntax and specifity.
You can use custom conditions
to create multi-theme tokens, and since you can nest them, you can create as many combinations as you want.
export default { light: '[data-color-mode=light] &', dark: '[data-color-mode=dark] &', pinkTheme: '[data-theme=pink] &', blueTheme: '[data-theme=blue] &',}
import { defineSemanticTokens } from '@pandacss/dev'
export default defineSemanticTokens.colors({ // ... text: { value: { _pinkTheme: { base: '{colors.pink.500}', _dark: '{colors.pink.300}' }, _blueTheme: { base: '{colors.blue.500}', _dark: '{colors.blue.300}' }, }, },})
<div data-theme="pink" data-color-mode="dark"> <h4 className={css({ color: 'text' })}>Pink dark</h4></div><div data-theme="blue" data-color-mode="dark"> <h4 className={css({ color: 'text' })}>Blue dark</h4></div>
<div data-theme="pink" data-color-mode="light"> <h4 className={css({ color: 'text' })}>Pink light</h4></div><div data-theme="blue" data-color-mode="light"> <h4 className={css({ color: 'text' })}>Blue light</h4></div>
Since Panda is based on CSS variables, that also means you can override the data-theme
anytime in the DOM, and it will automatically update the colors. You could even replace the theme colors by overriding the current CSS variables with a style={xxx}
attribute, useful for example if you want to let your users customize the colors at runtime.
One of the most powerful feature, yet not very well known, is the ability to create a virtual color palette from your tokens.
This allows you to refer to a dynamic color by its shade (50
to 900
in the built-in preset) or its semantic name, and it will automatically resolve to the current color palette.
import { css, cva, cx } from '../styled-system/css'
const button = cva({ base: { padding: 4, // you can also specify a default colorPalette in the `base` recipe key // colorPalette: 'blue', // ^^^^^^^^^^^^^^^^^^^^ }, variants: { variant: { primary: { bg: { base: 'colorPalette.500', _dark: 'colorPalette.200' }, color: { base: 'white', _dark: 'gray.900' }, }, }, }, defaultVariants: { variant: 'primary' },})
export const App = () => { return ( <> <div className="light"> <button className={cx(css({ colorPalette: 'blue' }), button())}>Click me</button> <button className={cx(css({ colorPalette: 'green' }), button())}>Click me</button> <button className={cx(css({ colorPalette: 'red' }), button())}>Click me</button> </div> <div className="dark"> <button className={cx(css({ colorPalette: 'blue' }), button())}>Click me</button> <button className={cx(css({ colorPalette: 'green' }), button())}>Click me</button> <button className={cx(css({ colorPalette: 'red' }), button())}>Click me</button> </div> </> )}
Combined with custom conditions
, you can create a multi-theme color palette that can also handle color modes (light/dark), statically. I went a bit crazy with this next example, but it shows the power of Panda CSS.
import { defineConfig } from '@pandacss/dev'
export default defineConfig({ // ... conditions: { extend: { mainTheme: '[data-theme=main] &', secondaryTheme: '[data-theme=secondary] &', }, }, theme: { extend: { semanticTokens: { colors: { text: { value: { _pinkTheme: { base: '{colors.pink.500}', _darkColorMode: '{colors.pink.300}' }, _blueTheme: { base: '{colors.blue.500}', _darkColorMode: '{colors.blue.300}' }, }, }, button: { info: { DEFAULT: { value: { _mainTheme: { base: '{colors.teal.500}', _dark: '{colors.teal.300}' }, _secondaryTheme: { base: '{colors.blue.500}', _dark: '{colors.blue.300}' }, }, }, }, warning: { DEFAULT: { value: { _mainTheme: { base: '{colors.yellow.500}', _dark: '{colors.yellow.300}' }, _secondaryTheme: { base: '{colors.orange.500}', _dark: '{colors.orange.300}' }, }, }, accent: { DEFAULT: { value: 'cyan' }, secondary: { value: { base: '{colors.sky.500}', _mainTheme: { base: '{colors.green.500}', _dark: '{colors.green.300}', }, _secondaryTheme: { base: '{colors.sky.500}', _dark: '{colors.sky.300}', }, }, }, }, }, }, }, }, }, },})
import { css, cva, cx } from '../styled-system/css'import { flex } from '../styled-system/patterns'
const button = cva({ base: { padding: 4, colorPalette: 'blue' }, variants: { variant: { // we can use CSS var fallbacks to handle different color palettes // setting the `bg` color in a CSS variable so we can use it in the dark mode as fallback shades: { '--bg': 'token(colors.colorPalette.500, colors.colorPalette.warning.accent.secondary)', bg: { base: 'var(--bg)', _hover: 'token(colors.colorPalette.700, colors.colorPalette.warning)', _dark: { base: 'token(colors.colorPalette.warning.accent, var(--bg))', _hover: 'token(colors.colorPalette.300, colors.colorPalette.warning)', }, }, color: { base: 'white', _dark: 'gray.900' }, }, warning: { bg: { base: 'colorPalette.warning', _hover: 'colorPalette.warning.accent', }, color: { base: 'white', _dark: 'gray.900' }, }, info: { bg: { base: 'colorPalette.info', hover: 'colorPalette.warning.accent.secondary', }, color: { base: 'white', _dark: 'gray.900' }, }, }, }, defaultVariants: { variant: 'shades' },})
export const App = () => { return ( <> <h4>No theme, variant shades</h4> <div className={cx('light', flex())}> <span className={css({ p: 4, display: 'inline-block', w: '120px' })}>Light mode</span> <button className={css(button.raw(), { colorPalette: 'blue' })}>Blue</button> <button className={css(button.raw(), { colorPalette: 'green' })}>Green</button> <button className={css(button.raw(), { colorPalette: 'red' })}>Red</button> </div>
<div className={cx('dark', flex())}> <span className={css({ p: 4, display: 'inline-block', w: '120px' })}>Dark mode</span> <button className={css(button.raw(), { colorPalette: 'blue' })}>Blue</button> <button className={css(button.raw(), { colorPalette: 'green' })}>Green</button> <button className={css(button.raw(), { colorPalette: 'red' })}>Red</button> </div>
<h4>Main theme</h4> <div className={cx('light', flex())} data-theme="main"> <span className={css({ p: 4, display: 'inline-block', w: '120px' })}>Light mode</span> <button className={css(button.raw({ variant: 'shades' }), { colorPalette: 'button' })}>Shades</button> <button className={css(button.raw({ variant: 'info' }), { colorPalette: 'button' })}>Info</button> <button className={css(button.raw({ variant: 'warning' }), { colorPalette: 'button' })}>Warning</button> </div> <div className={cx('dark', flex())} data-theme="main"> <span className={css({ p: 4, display: 'inline-block', w: '120px' })}>Dark mode</span> <button className={css(button.raw({ variant: 'shades' }), { colorPalette: 'button' })}>Shades</button> <button className={css(button.raw({ variant: 'info' }), { colorPalette: 'button' })}>Info</button> <button className={css(button.raw({ variant: 'warning' }), { colorPalette: 'button' })}>Warning</button> </div>
<h4>Secondary theme</h4> <div className={cx('light', flex())} data-theme="secondary"> <span className={css({ p: 4, display: 'inline-block', w: '120px' })}>Light mode</span> <button className={css(button.raw({ variant: 'shades' }), { colorPalette: 'button' })}>Shades</button> <button className={css(button.raw({ variant: 'info' }), { colorPalette: 'button' })}>Info</button> <button className={css(button.raw({ variant: 'warning' }), { colorPalette: 'button' })}>Warning</button> </div> <div className={cx('dark', flex())} data-theme="secondary"> <span className={css({ p: 4, display: 'inline-block', w: '120px' })}>Dark mode</span> <button className={css(button.raw({ variant: 'shades' }), { colorPalette: 'button' })}>Shades</button> <button className={css(button.raw({ variant: 'info' }), { colorPalette: 'button' })}>Info</button> <button className={css(button.raw({ variant: 'warning' }), { colorPalette: 'button' })}>Warning</button> </div> </> )}
ℹ️ Note that you could duplicate that button
object in the semanticTokens
and just change the color values to get another color palette, with the same nested semantic names, reacting to the same conditions.
e.g you could name it alert
and have alert.info
/ alert.warning
/ alert.warning.accent
/ alert.warning.accent.secondary
all able to handle light
/ dark
color modes for both the main
and secondary
theme.
This is the most common scenario, styling your appliction UI with Panda CSS.
While Panda brings many features to the table, you can choose to use a very small API surface, using only the css
function to style your components, and that’s it.
Panda definitely brings some cool features for CSS-in-JS
, but if that’s not your thing, that’s ok too.
Maybe colocation isn’t your thing at all ? It’s fine, you can keep your style objects in a separate file (.css.ts
?).
Panda comes with many tools for creating a design system
or component library. :
Using the panda.config
you can create a theme (tokens
/ breakpoints
/ textStyles
/ etc .. ) and even create your own utilities
/ conditions
/ patterns.
If needed, you can enforce type-safety by setting config.strictTokens
to true
.
The tokens (and semantic tokens) format is largely influenced by the W3C Token Format
Recipes and slot recipes are a great way to style reusable components that can be used in many different ways through their variants.
Patterns saves you from writing the same CSS or props over and over again.
If you prefer writing raw CSS, you can still leverage Panda’s token generator to generate CSS variables for each of your tokens
/semantic tokens
.
https://panda-css.com/docs/overview/why-panda#token-generator
Panda CSS being a styling engine, it’s not tied to any framework or library, you can use it anywhere. You could even build your own abstractions on top of it !
With the @pandacss/node
package, you can have access to the same API as we use in the CLI and VSCode extension. The PandaContext
contains lots of useful methods, for example you can generate each @layer
CSS independently, manipulate the TokenDictionary
, extract CSS from your code… You could create a bundler’s plugin with that, or you could probably even create your own extractor
and use it to generate CSS from your non-typescript code.
Speaking of the static @pandacss/extractor
, it actually doesn’t use any Panda-related code, its only dependency is ts-morph
(a wrapper around typescript
parser). You could also use it to extract static values from any function or JSX in your code.
It’s just the CSS you know already, using camelCase
instead of kebab-case
, with the additions of type-safety
autosuggestions for tokens
and conditions
while still allowing raw values and selectors.
There’s no need to learn a new syntax, no need to remember which class to use for whatever property, everything will feel very familiar and intuitive, in addition to the autocompletion and type-safety built-in.
Since all Panda does it generating a CSS, you can use it anywhere with our CLI
. ¹
We also provide a postcss
plugin to skip the CSS file import step and having another process (CLI with --watch
) to run in parallel.
While Panda brings many features to the table, you can choose to use a very small API surface, using only the css
function to style your components, and that’s it. If you’re not a fan of CSS-in-JS
, that’s also fine.
Maybe colocation isn’t your thing at all ? It’s fine, you can keep your style objects in a separate file. It just works.
Getting started is as easy as installing the CLI and running the panda init -p
command in your project.
pnpm add -D @pandacss/devpanda init -p
¹. A real-world example : I’m not really familiar with Angular, but it seems to require a dedicated integration to work with CSS-in-JS libraries. There’s even an issue opened to get one for Panda, but as the author mentioned, it’s not really needed since you can just use the CLI + the generated CSS file. Still, I can see the convenience !
Even though Panda CSS comes with batteries-included, you can customize it to fit your needs.
Using the panda.config
you can extend the default one or create your own theme (tokens
/ breakpoints
/ textStyles
/ etc .. ) and even create your own utilities
/ conditions
/ patterns.
You can disable/omit features that you don’t need, like shorthands
, jsxStyleProps
or even jsxFramework
entirely ! If you feel like type-safety
must be enforced, you can set strictTokens
to true
.
Panda can be distributed in different ways, depending on your needs.
We have a documentation page related to this topic: component library and there are a few examples in this repository
Using a preset is the easiest way to distribute your Design System (tokens
, breakpoints
, recipes
, etc) with other Panda users.
It’s as simple as picking a few options from your panda.config.ts
, creating a new preset.ts
file and exporting those options.
import { defineConfig } from '@pandacss/dev' import { definePreset } from '@pandacss/dev'
export default defineConfig({ export default definePreset({ theme: { extend: { tokens: { colors: { primary: { value: 'blue.500' } }, }, }, },})
You can then publish it to npm and anyone can use it by installing it and adding it to their own panda.config.ts
file.
import acmePreset from '@acme-org/panda-preset'import { defineConfig } from '@pandacss/dev'
export default defineConfig({ //... presets: ['@pandacss/dev/presets', acmePreset], // don't forget to keep the built-in presets !})
There are a few hand-picked presets listed in EcoPanda, by Abraham.
You can also distribute your library CSS by generating a static file using the CLI, and use it anywhere.
# generate the static CSS filepanda cssgen --outfile dist/styles.css
⚠️ This of course means that you shouldn’t directly expose any Panda API (
css
/recipes
/ etc..) to your users because it would not generate any additional CSS.
Panda allows you to create a component library, it could be intended for other Panda users or for anyone.
If you intend to distribute it to other Panda users:
preset
along with your components, so that your users can generate the CSS for your tokens
/ recipes
/ etcconfig.outdir
(defaults to styled-system
, for this example we’ll use @acme/styled-system
)config.importMap
to be the same as your outdir package name ( @acme/styled-system
)import { definePreset } from '@pandacss/dev'
export default definePreset({ theme: { extend: { tokens: { colors: { primary: { value: 'blue.500' } }, }, }, },})
import { defineConfig } from '@pandacss/dev'import acmePreset from '@acme/panda-preset'
export default defineConfig({ presets: [acmePreset], importMap: '@acme/styled-system',})
import { defineConfig } from '@pandacss/dev'import acmePreset from '@acme/panda-preset'
export default defineConfig({ presets: [acmePreset], importMap: '@acme/styled-system', include: ['./src/**/*.{js,jsx,ts,tsx}'],})
This is because:
importMap
to know which imports should matching your theme (tokens
/ recipes
/ etc..)module specifier
(the right part of imports, e.g abc
in import xxx from 'abc'
), your bundler (vite
for example) will be able to deduplicate the lightweight JS runtime (config.outdir
) between the one from your library and the one from your users.If you don’t share the same module specifier, users will end up bundling the code for 2 different css
functions, cva
etc; one from your library (@acme/styled-system
) and one from your users outdir (styled-system
by default) which is not ideal.
set your outdir package name (@acme/styled-system
) as external in your build step so that you don’t bundle it with your components, ex: tsup src/index.tsx --external @acme-org/styled-system
you can expose any Panda API (css
/ recipes
/ etc..)
Summary:
preset
so people can use your design system tokens@acme-org/styled-system
) so your users can override or extend your design system tokens@acme-org/components
) using the @acme-org/styled-system
as external
There’s a recent trend of creating a component library and distributing it as a copy-paste
solution.
For example, Park UI and Shadow Panda (shadcn-ui
equivalent using Panda) are both component libraries that only rely on a preset
published on npm and then you can just pick the components you need and copy-paste them in your project.
Let’s take a little break. If you reached this far, you probably have a good idea of what Panda CSS
is, and what it can do for you.
The part below is mostly concise explanations/examples of what the documentation already covers (I shamelessly took small parts of it), so that you can have a quick overview/reference of the API surface.
This should also help searching for a specific feature, since the documentation is pretty long, and this is a one-page reference searchable with cmd+f
.
If you want to learn more about a specific topic, I encourage you to read the related documentation, it’s pretty good !
What better way to showcase what Panda CSS can do than with some concrete examples ?
The css
function is the main API of Panda CSS, it’s used to generate CSS from your style objects.
In the static analysis step we extract each arguments, resolving each style object property/value combinations, and generating an atomic CSS rule for each of them.
At runtime, it transforms the object-syntax (that you provide) to a class string composed of atomic classes. Most Panda features are using the css
function under the hood. (cva
, recipes
, patterns
, styled
factory, etc…). That means that anything you can do with the css
function, you can do the same in those features.
Property keys can be: any config breakpoints
/ conditions
/ utility
/ shorthands
/ arbitrary
selectors / CSS built-in properties / CSS variables
Property values can be: any config tokens
/ semantic tokens
/ utility
specific values / arbitrary values / CSS built-in values / conditions as object / token path (only when using a CSS variable as key)
Has a .raw()
function to mark the object as a style object for the Panda static extractor
Can use the inline token(value, fallback)
function that will be transformed at compile-time to a CSS variable (using postcss)
Will smartly merge each arguments together, including css.raw()
, {cvaFn}.raw()
or {patternFn}.raw()
calls
// in panda.config.tsimport { defineConfig } from '@pandacss/dev'
export default defineConfig({ // ... theme: { extend: { tokens: { colors: { blue: { 300: { value: '#93c5fd' }, }, }, }, }, },})
// in app.tsximport { css } from 'styled-system/css'
const className = css({ color: 'blue.300' })// => `text_blue_300`// will create a CSS var `--colors-blue-300` with a value set to `#93c5fd`
import { css } from 'styled-system/css'
// in panda.config.tsimport { defineConfig } from '@pandacss/dev'
export default defineConfig({ // ... theme: { extend: { semanticTokens: { colors: { primary: { value: '{colors.blue.300}' }, }, }, }, },})
// in app.tsximport { css } from 'styled-system/css'
const className = css({ color: 'primary' })// => `text_primary`// will create a new CSS variable `--colors-primary` that itself references `--colors-blue-300`
// in panda.config.tsimport { defineConfig } from '@pandacss/dev'
export default defineConfig({ // ... utility: { extend: { display: { className: 'd', }, backgroundColor: { shorthand: 'bgColor', className: 'bg', values: 'colors', },
debug: { className: 'debug', values: { type: 'boolean' }, transform(value) { if (!value) return {} return { outline: '1px solid blue !important', '&>*': { outline: '1px solid red !important', }, } }, }, }, },})
// in app.tsximport { css } from 'styled-system/css'
const className = css({ display: 'flex', debug: true, bgColor: 'red' })// => `d_flex debug bg_red`
import { css } from 'styled-system/css'
const className = css({ color: '#fde047', // you can use any CSS value bg: 'gray.800!', // you can mark any value as important with `!` or `!important` // => `text_#fde047 bg_gray.800!`
// you can use composite values with the inline `token` function border: '1px solid token(colors.blue.300)',
// when using a CSS variable as key, the value can be a token path '--my-color': 'colors.blue.400', '--my-font-size': 'fontSizes.2xl',})
// in panda.config.tsimport { defineConfig } from '@pandacss/dev'
export default defineConfig({ // ... conditions: { extend: { dark: '.dark &', // any child of a `.dark` element, hover: '&:hover', // any hovered element highlighted: '&:is([data-highlighted], :focus)', // you can use `:is` to combine selectors }, }, theme: { extend: { breakpoints: { md: '768px', }, }, },})
// in app.tsximport { css } from 'styled-system/css'
const className = css({ color: 'blue.800', _dark: { // condition object color: 'green.300', // you can nest conditions indefinitely _hover: { color: 'red.500', bg: 'gray.800', }, }, fontSize: { // property-based conditions base: '16px', // base means the current condition path (e.g nothing here) md: '18px', // this is a breakpoint, it's not prefixed with `_` _highlighted: '2xl', }, '&[data-selected]': { // arbitrary conditions are also fine ! bg: 'blue.500', },})
// in styles.tsimport { css } from 'styled-system/css'
export const styles = css.raw({ color: 'blue.300', bg: 'white' })// returns the object as is, serves as a marker for the static extractor// especially useful to export styles and use them in other files
// button.tsimport { styles } from './styles'
export const Button = (props) => { return <button className={css(styles(), props.css)}>Click me</button>}
// app.tsimport { Button } from './button'
export const App = () => { return <Button css={{ color: 'red.500' }} /> // would result in { color: 'red.500', bg: 'white' }}
You can use the cx
function to concatenate multiple class names together.
import { css, cx } from 'styled-system/css'
const cardClass = 'my-card'const className = cx(cardClass, css({ color: 'blue.300' }))// => `my-card text_blue.300`
cva
allows you to create a re-usable styles based on variants, that still produces atomic CSS classes. They’re the atomic equivalent of the config recipe
.
cva
base and variants styles will be always be generated, no matter if they’re used or not.
You can read more here to understand the difference between recipes and cva.
Inside the base
and variants
styles, you can use anything that you can use in the css
function and even merge multiple css
and cva
together.
export const badge = cva({ base: { fontWeight: 'medium', px: '3', rounded: 'md', }, variants: { status: { default: { color: 'white', bg: 'gray.500', }, success: { color: 'white', bg: 'green.500', }, warning: { color: 'white', bg: 'yellow.500', }, }, }, defaultVariants: { status: 'default', },})
const className = badge({ status: 'success' })// => `font_medium px_3 rounded_md text_white bg_green.500`
import { css, cx, cva } from 'styled-system/css'
const overrideStyles = css.raw({ bg: 'red', color: 'white',})
const buttonStyles = cva({ base: { bg: 'blue', border: '1px solid black', }, variants: { size: { small: { fontSize: '12px' }, }, },})
const className = css( // returns the resolved style object buttonStyles.raw({ size: 'small' }), // add the override styles overrideStyles,)
// => 'bg_red border_1px_solid_black fs_12px text_white'
tl;dr: Like cva
but scoped with a className instead of generating atomic CSS classes.
config recipes
allows you to create a re-usable styles based on variants, including responsive variants, that produce scoped CSS classes. They’re the scoped equivalent of the cva
atomic fn.
config recipes
base and variants styles will be generated JIT, as in, only when found in the source files.
Since config recipes
are scoped by a className that you provide, they generate very predictable CSS rules, which makes them very easy to override using raw CSS. This can be particularly useful when creating a component library.
You can read more here to understand the difference between recipes and cva.
Inside the base
and variants
styles, you can use anything that you can use in the css
function.
tl;dr: Like config recipes
but with slots
, allowing you to style multipart components.
This is a special kind of recipe that allows you to create a re-usable styles based on slots
, that produce scoped CSS classes. Each slot should map to a component part, for example a button
could have a icon
slot, a label
slot, a loading
slot, etc.
These slots can then be styled together, reacting to the same variants. For example, your button
could have a size
variant that would apply to both the icon
and label
slots, slightly changing their font-size and padding.
The same upside and downsides from the config recipes
apply here.
You can read more here to understand the difference between config (slot) recipes and sva.
// in button.recipe.ts
import { defineSlotRecipe } from '@pandacss/dev'
export const button = defineSlotRecipe({ className: 'title', slots: ['root', 'label', 'icon'], base: { root: { display: 'flex', rounded: 'md' }, label: { fontWeight: 'medium', px: '3' }, icon: { mr: '2' }, }, variants: { size: { small: { icon: { mr: '1' }, label: { fontSize: '12px' }, }, md: { label: { fontSize: '15px' }, }, }, }, defaultVariants: { size: 'md', },})
// in panda.config.ts
import { defineConfig } from '@pandacss/dev'import { button } from './button.recipe'
export default defineConfig({ // ... theme: { extend: { slotRecipes: { button, }, }, },})
// in app.ts
import { button } from 'styled-system/recipes'
const className = button({ size: 'small' })// => `button--size_small`
tl;dr: Like cva
but for slot recipes
.
This is a special kind of recipe that allows you to create a re-usable styles based on slots
, that produce atomic CSS classes. Each slot should map to a component part, for example a button
could have a icon
slot, a label
slot, a loading
slot, etc.
These slots can then be styled together, reacting to the same variants. For example, your button
could have a size
variant that would apply to both the icon
and label
slots, slightly changing their font-size and padding.
The same upside and downsides from the cva
fn apply here.
You can read more here to understand the difference between config (slot) recipes and sva.
import { sva } from 'styled-system/recipes'
const className = sva({ slots: ['root', 'label', 'icon'], base: { root: { display: 'flex', rounded: 'md' }, label: { fontWeight: 'medium', px: '3' }, icon: { mr: '2' }, }, variants: { size: { small: { icon: { mr: '1' }, label: { fontSize: '12px' }, }, md: { label: { fontSize: '15px' }, }, }, }, defaultVariants: { size: 'md', },})
When using a JSX
-like framework, you can use the styled
factory to create a styled component
, e.g a component that can receive JSX style props
.
style props
are just properties matching the same name as your css()
function keys (which means any breakpoints
/ conditions
/ utility
/ shorthands
/ CSS built-in property).
⚠️ You should NOT rename JSX properties, like using myColor={color}
instead of color={color}
. This is due to static limitations
You can use the 2nd
argument of the styled
factory to create an inline atomic recipe
or re-use an existing one. (could also be a config recipe
)
You can also create a styled
component using another one as basis (1st arg), and the previous atomic recipe
base and variants styles will automatically be deep merged with the new one. (new wins in case of conflict)
Finally, the 3rd
argument of the styled
factory allows you to provide default props for your component or override which props should be forwarded to the underlying DOM element.
import { styled } from '../styled-system/jsx'
const StyledButton = styled('button')
const App = () => ( <StyledButton bg="blue.500" color="white" py="2" px="4" rounded="md"> Button </StyledButton>)
import { styled } from '../styled-system/jsx'
const Button = styled( 'button', { base: { py: '2', px: '4', rounded: 'md', }, variants: { variant: { primary: { bg: 'blue.500', color: 'white', }, secondary: { bg: 'gray.500', color: 'white', }, }, }, }, { defaultProps: { 'data-testid': 'button123', variant: 'primary', }, },)
const App = () => ( <Button variant="secondary" mt="10px"> Button </Button>)
// in panda.config.tsimport { defineConfig } from '@pandacss/dev'
export default defineConfig({ // ... jsxStyleProps: 'minimal', jsxFactory: 'panda', // ℹ️ you can rename the `jsx` factory to `panda` if you want (defaults to `styled`)})
// in app.tsimport { panda } from '../styled-system/jsx'
const StyledButton = panda('button')
const App = () => ( // ✅ only the `css` property is available when using `jsxStyleProps: "minimal"` // can be useful to reduce the noise in the auto-completion <StyledButton css={{ bg: 'blue.500', color: 'white', py: '2', px: '4', rounded: 'md' }}>Button</StyledButton>)
// https://play.panda-css.com/JUa2RxiZleimport { cx, cva } from 'styled-system/css'import { styled } from 'styled-system/jsx'import { center, stack } from 'styled-system/patterns'
const button = cva({ base: { borderRadius: 'md', fontWeight: 'semibold', h: '10', px: '4', }, variants: { visual: { solid: { bg: { base: 'colorPalette.500', _dark: 'colorPalette.300' }, color: { base: 'white', _dark: 'gray.800' }, }, }, },})
const sizes = cva({ base: { colorPalette: 'blue' }, variants: { size: { lg: { px: '8', fontSize: '24px' }, }, },})
const StyledButton = styled('button', button)const Merged = styled(StyledButton, sizes)
const App = () => { return ( <Merged visual="solid" size="lg"> With size merged </Merged> )}
Patterns are layout primitives that you can use to avoid writing the same CSS over and over again. They also have access to the same features as the css()
function, with the addition of specific utility
props per pattern (e.g direction
that maps to the flexDirection
property inside of the flex
pattern config).
If using a config.jsxFramework
, Panda will also generate JSX patterns
, matching the function version. (e.g flex
and <Flex />
)
import { flex } from '../styled-system/patterns'import { Stack } from '../styled-system/jsx'
const App = () => ( <div css={flex({ direction: 'row', justify: 'center', align: 'center' })}> <Stack gap="4"> <div>Item 1</div> <div>Item 2</div> <div>Item 3</div> </Stack> <Stack gap="4"> <div>Item 1</div> <div>Item 2</div> <div>Item 3</div> </Stack> </div>)
You can read more on customizing patterns and see the list of built-in patterns here
With utilities
, you can create your own CSS properties that can leverage the same features as the css()
function: tokens
, conditions
, etc…
For example, one of our most requested feature is integrating Color opacity modifiers, so that you could do something like color: "red.200/50"
and it would change the alpha
value to 0.5
of the red.200
color.
It’s actually pretty easy to do so with a custom utility
:
// credit goes to shadow-panda// https://github.com/kumaaa-inc/shadow-panda/blob/f8f8afc445ccf12d78b089f80afff4462596f8c4/packages/preset/src/utilities/background-alpha.ts
import type { UtilityConfig } from '@pandacss/types'import { colorMix } from './color-mix.ts'
export const backgroundAlpha: UtilityConfig = { backgroundAlpha: { shorthand: ['bga'], property: 'backgroundColor', className: 'background-alpha', values: { type: 'string' }, transform: (...args) => { const { value, color } = colorMix(...args)
return { '--sp-bga': value, backgroundColor: `var(--sp-bga, ${color})`, } }, },}
// credit goes to shadow-panda// https://github.com/kumaaa-inc/shadow-panda/blob/f8f8afc445ccf12d78b089f80afff4462596f8c4/packages/preset/src/lib/color-mix.ts
import type { PropertyTransform } from '@pandacss/types'
export const colorMix: (...args: Parameters<PropertyTransform>) => { color: string amount: string | number value: string} = (value: string, { token }) => { const [color, opacityAmount] = value.split('/') const amount = !isNaN(Number(opacityAmount)) ? Number(opacityAmount) : 100 const colorValue = token(`colors.${color}`) const opacityValue = token(`opacity.${amount}`) const amountValue = opacityValue ? Number(opacityValue) * 100 : `${100 - amount}%`
return { color: colorValue ?? color, amount: amountValue, value: `color-mix(in srgb, transparent ${amountValue}, ${colorValue})`, }}
import { css } from 'styled-system/css'
const className = css({ backgroundAlpha: 'red.300/50' })// will result in the following CSS:
// .background-alpha_red\.300\/50 {// --sp-bga: color-mix(in srgb, transparent 50%, var(--colors-red-300));// background-color: var(--sp-bga, var(--colors-red-300));// }
// you can try it here https://play.panda-css.com/pnaS3don-r
There are other use cases for utilities
, like enum values
, mapped values
or even boolean values.
You can read more on customizing utilities and see the list of built-in utilities here
This is a built-in utility, included in the base preset, that you can use to debug your styles, it will add an outline
attribute to the element and all its children. Simple, but quite useful !
<div className={center({ h: 'full' })}> <div className={css({ p: '6', m: '4', w: 'md', boxShadow: 'md', borderRadius: 'md', _dark: { bg: '#262626', color: 'white' }, debug: true, })} > <div className={css({ textStyle: 'xl', fontWeight: 'semibold', pb: '2', })} > Team Members </div> <div className={css({ textStyle: 'lg', })} > Content </div> </div></div>
If you wonder how it works, here’s the full code of the debug
utility:
import { defineConfig } from '@pandacss/dev'
export default defineConfig({ // ... utilities: { extend: { debug: { className: 'debug', values: { type: 'boolean' }, transform(value) { if (!value) return {} return { outline: '1px solid blue !important', '&>*': { outline: '1px solid red !important', }, } }, }, }, },})
The debug utility is already in the built-in presets so you don’t need to add it manually !
Panda CSS provides a token
function that you can use in your runtime JS code to reference a token value.
You can specify a fallback
argument that will be used if the token value is not found.
import { token } from 'styled-system/css'
const blue = token('colors.blue.300')// => `#93c5fd`
const App = (props) => { const color = token(`colors.${props.color}.300`, 'red') return <div style={{ color }}>Hello world</div>}
It also has a .var()
function that will return the token CSS variable instead of a value.
import { token } from 'styled-system/css'
const blue = token('colors.blue.300')// => `var(--colors-blue-300)`
const App = (props) => { const color = token.var(`colors.${props.color}.300`, 'red') return <div style={{ color }}>Hello world</div>}
And then there’s the string token
function, which is a compile-time version of the runtime token
function mentioned above.
Using the inline token
fn, you can also create composite values, like a border
that would be composed of a width
, style
and a color
.
import { css } from 'styled-system/css'
css({ border: '2px solid token(colors.blue.300)' })
// is equivalent tocss({ borderWidth: '2px', borderStyle: 'solid', borderColor: 'blue.300' })
And can also be used in at-rule
values, like @media
:
import { css } from 'styled-system/css'
const className = css({ '@media screen and (min-width: token(sizes.4xl))': { color: 'green.400', },})
When using a JSX framework, any property matching the same name as your css()
function keys (which means any breakpoints
/ conditions
/ utility
/ shorthands
/ CSS built-in property) will be extracted by the static extractor and transformed to a class string.
The static extractor will extract every PascalCased
component, so that things “just work” out of the box.
You can use the
jsxStyleProps
config to customize which style props are available to your components:all
/minimal
(only thecss
property) /none
The css
property is a special property that will always be extracted without the need for the .raw
marker. You can use it to dynamically merge styles together.
Since every PascalCased component will be extracted, as long as what you pass in the css
property remains statically analyzable, everything will work as expected without any effort.
const Button = (props) => { const { css: cssProp, ...rest } = props
return <button {...rest} className={css({ p: 4, color: 'blue.300' }, props.css)} />}
const App = () => { return <Button css={{ color: 'red.500' }} /> // would result in { p: 4, color: 'red.500' }}
See raw()
See the first static extractor example or more examples in the docs.
Panda supports CSS nesting, which means that you can use the &
syntax to target children and siblings.
css({ bg: 'red.400', '& span': { color: 'pink.400', // can nest indefinitely // please don't do that though },})
Quoting the docs:
⚠️ We recommend not using descendant selectors as they can lead to specificity issues when managing style overrides. Colocating styles directly on the element is the preferred way of writing styles in Panda.
See virtual color
Tokens are at the core of the Panda CSS design system. They’re a way to define your design system values in a centralized place, and then reference them in your styles.
Design tokens in Panda are largely influenced by the W3C Token Format
They are grouped in predefined categories, the exhaustive list is: zIndex, opacity, colors, fonts, fontSizes, fontWeights, lineHeights, letterSpacings, sizes, shadows, spacing, radii, borders, durations, easings, animations, gradients, assets
Docs: https://panda-css.com/docs/theming/tokens
import { defineTokens } from '@pandacss/dev'
export default defineTokens({ colors: { primary: { value: '#0FEE0F' }, secondary: { value: '#EE0F0F' }, }, fonts: { body: { value: 'system-ui, sans-serif' }, }, sizes: { small: { value: '12px' }, medium: { value: '16px' }, large: { value: '24px' }, },})
Same as tokens but can reference other tokens.
Docs: https://panda-css.com/docs/theming/tokens#semantic-tokens
import { defineSemanticTokens } from '@pandacss/dev'
export default defineSemanticTokens({ colors: { danger: { value: { base: '{colors.red.500}', _dark: '{colors.red.200}' }, }, success: { value: { base: '{colors.green.500}', _dark: '{colors.green.300}' }, }, muted: { value: { base: '{colors.gray.500}', _dark: '{colors.gray.300}' }, }, canvas: { value: '{colors.white}' }, },})
css
function, cva
, recipes
, patterns
, styled
factory, etc… nestable_
prefix to differentiate themDocs: https://panda-css.com/docs/concepts/conditional-styles
import { css } from 'styled-system/css'
const className = css({ color: 'blue.800', _dark: { // condition object color: 'green.300', // you can nest conditions indefinitely _hover: { color: 'red.500', bg: 'gray.800', }, }, fontSize: { // property-based conditions base: '16px', // base means the current condition path (e.g nothing here) md: '18px', // this is a breakpoint, it's not prefixed with `_` _highlighted: '2xl', }, '&[data-selected]': { // arbitrary conditions are also fine ! bg: 'blue.500', },})
import { css } from 'styled-system/css'
const className = css({ color: 'blue.800', _hover: { color: 'red.500', _disabled: { color: 'gray.500', }, },})
// you could also write it in the property-based syntax
const className = css({ color: 'blue.800', _hover: { color: 'red.500', _disabled: { color: 'gray.500', }, },})
// https://panda-css.com/docs/customization/conditionsimport { defineConfig } from '@pandacss/dev'
export default defineConfig({ conditions: { extend: { groupHover: '[role=group]:where(:hover, [data-hover]) &', withTestid: '&[data-testid]', pinkTheme: '[data-theme=pink] &', }, },})
import { css } from 'styled-system/css'
const className = css({ // `&` means the current element '&:focus': { color: 'red.500', }, // can use any CSS selector '&[data-selected]': { bg: 'blue.500', }, // targeting children is possible (⚠️ but NOT recommended) '& span': { bg: 'blue.500', }, // you can use multiple selectors '&[data-highlighted], :hover': { fontSize: '2xl', }, // or even an `at-rule` '@media screen and (min-width: 1024px)': { color: 'green.300', }, // and reference a token path inside of it '@media screen and (min-width: token(sizes.4xl))': { color: 'green.300', },})
A special kind of conditions
that allows you to create responsive styles and directly maps to CSS @media
queries.
Docs: https://panda-css.com/docs/concepts/responsive-design
<span className={css({ fontWeight: 'medium', lg: { fontWeight: 'bold' }, // property-based conditions fontWeight: { base: 'medium', lg: 'bold' } // arbitrary at-rules are fine '@media screen and (min-width: 1024px)': { color: 'green.300', }, })}> Text</span>
// Panda also accepts arrays as values for responsive styles.// Pass the corresponding value for each breakpoint in the array. Using our previous code as an example:<span className={css({ // We're leaving the corresponding values of the unused breakpoints md and lg as undefined. fontWeight: ['medium', undefined, undefined, 'bold'], })}> Text</span>
<span className={css({ // range fontWeight: { mdToXl: 'bold' }
// single fontWeight: { lgOnly: 'bold' } })}> Text</span>
Docs: https://panda-css.com/docs/utilities/display#hiding-elements
<div className={css({ display: 'flex', hideFrom: 'md' })} /><div className={css({ display: 'flex', hideBelow: 'md' })} />
textStyles
are a practical way to compose styles for typography properties (fontSize
, fontWeight
, lineHeight
, letterSpacing
, etc…) under a single utility.
Docs: https://panda-css.com/docs/theming/text-styles
// in panda.config.ts
import { defineConfig } from '@pandacss/dev'
export default defineConfig({ // ... theme: { extend: { textStyles: { heading: { value: { fontSize: '4xl', fontWeight: 'bold', lineHeight: '1.2', }, }, }, }, },})
// in app.tsimport { css } from 'styled-system/css'
const className = css({ textStyle: 'heading' })// => `textStyle_heading`
// and generate the following CSS:// .textStyle_heading {// font-size: var(--font-sizes-4xl);// font-weight: var(--font-weights-bold);// line-height: 1.2;// }
Shorthands
are a way to create shorter names for properties that you use often. They are enabled by default in the config and the built-in presets
come with a few of them.
If you prefer not to use them, you can disable them by setting shorthands: false
in your config.
Docs: https://panda-css.com/docs/concepts/writing-styles#shorthand-properties
import { css } from '../styled-system/css'
// without shorthandsconst styles = css({ backgroundColor: 'gainsboro', padding: '10px 15px', borderRadius: '9999px',})
// with shorthandsconst styles = css({ bg: 'gainsboro', p: '10px 15px', rounded: '9999px',})
Docs: https://panda-css.com/docs/customization/theme#keyframes
import { defineConfig } from '@pandacss/dev'
export default defineConfig({ theme: { extend: { keyframes: { fadein: { '0%': { opacity: '0' }, '100%': { opacity: '1' }, }, fadeout: { '0%': { opacity: '1' }, '100%': { opacity: '0' }, }, }, }, },})
without surprise, will generate the following CSS:
@keyframes fadein { 0% { opacity: 0; }
100% { opacity: 1; }}
@keyframes fadeout { 0% { opacity: 1; }
100% { opacity: 0; }}
Panda CLI, included when you install @pandacss/dev
, allows you to generate CSS in any project that has a panda.config.{js,ts,etc}
file.
Although the postcss plugin is very convenient, it’s not required to use Panda CSS. You can use the CLI to generate the CSS and runtime, and then use them in your project however you want.
CLI reference is here.
Here’s a quicky summary:
panda init
will help you get started with a panda.config.ts
, or can also be used if you don’t remember the @layer
order
panda
will generate the CSS (from your config.include
) + and lightweight runtime in your config.outdir
, and watch for changes with the -w
or --watch
flag. It’s the same as panda cssgen && panda codegen
panda cssgen
will only generate the CSS (from your config.include
)
panda cssgen [artifact]
, will only generate the CSS related to the given artifact
, which can be one of: preflight
| tokens
| static
| global
| keyframes
panda cssgen --minimal
will only generate the CSS related to the style usage found in your config.include
panda codegen
will generate the lightweight runtime in your config.outdir
panda debug
will generate 1 .css
file and 1 .json
file for each of your source files, allowing you to see what was generated (css) and what was extracted (json) by file.
panda ship
will generate 1 .json
file that contains the summary of the style usage found from your source files. You can then use this .json
file in any config.include
as if you had included the source files, which can be useful to distribute the CSS of a npm package using Panda without publishing the source files.
ℹ️ If you’re concerned about the performance of the postcss plugin, the CLI is a great alternative. We don’t have much control over how often the postcss plugin will be called, but we can control how many times we call the CLI. (e.g only when we need to)
We also have an online playground that you can use to try out Panda CSS using the basic examples and see if it fits your needs.
There’s also special tab named AST
at the bottom of the screen in which you can see exactly what the Panda extractor sees, which property and values were resolved, there’s also a CSS
tab what CSS will be generated from your code.
These are great tools to debug your code and understand how Panda works. You can also open your developer tools (F12) to see the Panda context (just like the one used in the CLI through @pandacss/node
) that handles the CSS and artifact generation.
I hope this post will help give more exposure to Panda CSS and its many features. I’m really excited to see what people will build with it and how it will evolve in the future.
I’m happy to answer any specific questions you have about Panda CSS. Feel free to @ me on Twitter or on Panda CSS discord.