"Why would I choose Panda CSS ?"

And why it's easier to answer this question

It’s another question that I see a lot, a bit different than the previous one.

The previous question was more about comparing two solutions (what does Panda has that xxx doesn’t and vice-versa). Since I wanted this question to be fair from both sides by anyone debating on the topic, and as I am pretty knowledgeable with Panda; I mostly wrote about Panda’s limitations, their reasons and some workarounds.

This time, I feel like this question is more about what strengths Panda has to offer. And I’ve got tons of answers for that !


This post will be split in 4 parts:

1. An overview of the main features
2. A look at the possible use-cases
3. A summary of the DX
4. A concise API reference with examples

So, from the endless list of styling solutions and CSS-in-JS libraries, why should you choose Panda?

Build-time CSS

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.

Atomic CSS

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;
}
}

Built-in Type-safety

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: true
import { 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>
)
}

Batteries included

Built-in presets

We provide 2 different presets out of the box (you don’t need to install anything):

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 !

No additional tooling

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.

Extendable

Theming

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.

Outdir

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.

CSS generation

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.

Config

You can see the full list of config options here: https://panda-css.com/docs/references/config

CSS-in-JS

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

RSC compatible

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. 😁

Style props

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>
)
}

Merging styles

Related docs : https://panda-css.com/docs/concepts/merging-styles

css merging

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' }

raw() marker

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.

file-1.ts
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
file-2.ts
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' }

cx

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

Static analysis

Pit of success

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 !

Smart

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 !

Extractor Examples

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>
)
}

There are of course still some limitations to static analysis, I wrote a bit about it here

Runtime dynamic styling

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

Using CSS variables

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>
)
}

Using variants

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' },
},
},
})

Best practices

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" }.

Static generator

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

Modern

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.

Multi-Theme tokens

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.

Pink dark

Blue dark

Pink light

Blue light

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.


Virtual color palette

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.

Multi-theme color palette

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.

ℹ️ You can hover the buttons for even more madness !

No theme, variant shades

Light mode
Dark mode

Main theme

Light mode
Dark mode

Secondary theme

Light mode
Dark mode

ℹ️ 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.


Use cases

Styling solution

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 ?).

Design-system / Component library

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.

Token generator

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

Styling engine

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.

Static Extractor

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.


Developer experience (DX)

Easy to learn

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.

Easy to use

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.

Terminal window
pnpm add -D @pandacss/dev
panda 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 !

Easy to customize

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.

Easy to distribute

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

Preset

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.

preset.ts
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.

panda.config.ts
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.

CSS file

You can also distribute your library CSS by generating a static file using the CLI, and use it anywhere.

Terminal window
# generate the static CSS file
panda 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.

Component library

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:

  • you should distribute a preset along with your components, so that your users can generate the CSS for your tokens / recipes / etc
  • make a package dedicated to the config.outdir (defaults to styled-system, for this example we’ll use @acme/styled-system)
  • both you and your users must set your config.importMap to be the same as your outdir package name ( @acme/styled-system)
packages/preset/index.ts
import { definePreset } from '@pandacss/dev'
export default definePreset({
theme: {
extend: {
tokens: {
colors: { primary: { value: 'blue.500' } },
},
},
},
})

This is because:

  1. Panda uses the importMap to know which imports should matching your theme (tokens / recipes / etc..)
  2. by sharing the same 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:

  1. publish a Panda preset so people can use your design system tokens
  2. an outdir package (@acme-org/styled-system) so your users can override or extend your design system tokens
  3. and of course your components (@acme-org/components) using the @acme-org/styled-system as external

Copy-paste

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.


Interlude

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 !


API examples

What better way to showcase what Panda CSS can do than with some concrete examples ?

css

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.ts
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
// ...
theme: {
extend: {
tokens: {
colors: {
blue: {
300: { value: '#93c5fd' },
},
},
},
},
},
})
// in app.tsx
import { 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`

cx

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

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`

recipes

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.

slot recipes

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`

sva

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',
},
})

styled

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>
)

patterns / jsx patterns

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

utility

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})`,
}
},
},
}

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

Debug utility

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 !

Team Members
Content

Runtime token()

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>
}

Compile-time string token()

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 to
css({ 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',
},
})

JSX style props

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 the css property) / none

JSX css prop

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' }
}

.raw(xxx) merging

See raw()

Runtime conditions

See the first static extractor example or more examples in the docs.

CSS nesting

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.

colorPalette

See virtual color

Theme

tokens

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

  • Each token will create a CSS variable
  • You can nest tokens indefinitely
  • Nested tokens can be used to create groups and will generate dot-separated names

Docs: https://panda-css.com/docs/theming/tokens

tokens.ts
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' },
},
})

semantic tokens

Same as tokens but can reference other tokens.

Docs: https://panda-css.com/docs/theming/tokens#semantic-tokens

semantic-tokens.ts
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}' },
},
})

conditions

  • You can use them with the css function, cva, recipes, patterns, styled factory, etc… nestable
  • Every condition starts with a _ prefix to differentiate them
  • You can use a condition object or a property-based condition
  • You can nest conditions indefinitely
  • You can find the list of built-in conditions here.

Docs: 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',
},
})

breakpoints

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>

textStyles

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.ts
import { 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

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 shorthands
const styles = css({
backgroundColor: 'gainsboro',
padding: '10px 15px',
borderRadius: '9999px',
})
// with shorthands
const styles = css({
bg: 'gainsboro',
p: '10px 15px',
rounded: '9999px',
})

keyframes

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;
}
}

CLI

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)

Online Playground

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.


Conclusion

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.

Back to top