Deep dive: Panda CSS
The cutest styling library in town?
The CSS landscape is constantly changing and it can all be a touch overwhelming; we've come such a long way since the days of a single unwieldy stylesheet styling an entire website and there are so many technologies out there, it's hard to keep track. One of those technologies is Panda CSS and I think it's an interesting one. This deep dive has been a long time in the making, I hope you like it. Let's get stuck in!
What is Panda CSS?
Other than having a cute name and mascot, Panda CSS is a "build time CSS-in-JS library" - let's break down what that means. CSS-in-JS is a technology that allows developers to write their CSS alongside their JavaScript code. No more separate CSS files with all the CSS rules in them - styles are defined right in your JavaScript. Years ago this would have been sacrilege (separation of concerns was the ultimate aim!), but JSX changed all that and these days nobody bats an eyelid at keeping our concerns nice and cosy.
As for the "build time" part, this is in contrast to libraries like styled components or Emotion, which are run time CSS-in-JS libraries. In Panda CSS, all styles are generated at build time and injected into the HTML, meaning they're there and ready to go when a user loads the page. Run time CSS-in-JS libraries, on the other hand, generate all the CSS in the browser when the user loads up the app. This means a lot of heavy lifting is done on the client which can cause performance issues if you're not careful.
Why does it exist?
There has been a shift back to the server in the front end world in the last few years - the advent of React Server Components has meant a whole paradigm shift, not just in writing React components, but also when it comes to styling.
In order to get performance gains, we are now trying to run as little of our code on the client as we can get away with. It means re-architecting entire applications and it also means the old run-time libraries like styled-components and Emotion simply won't do - anywhere we use runtime CSS-in-JS will have to run in the client, meaning we can't use React Server Components and gain all those juicy performance boosts.
This is why Panda CSS (and its competitors Linaria, Vanilla Extract and StyleX) were created - all of these tools don't need the client for styling and are therefore React Server Components compatible.
Okay, how do I set it up?
Because Panda CSS is a build time tool, we'll want to install it as a dev dependency:
npm install -D @pandacss/devOnce that's done, Panda provides a CLI command that will initialise your project for you:
npx panda initThis will create a panda.config.ts file and update your .gitignore file to include the styled-system directory (more on this later).
Now you'll want to add a prepare script to your package.json file as follows:
"scripts": {
"prepare": "panda codegen && panda cssgen",
}You'll want to run this script now that it's in:
npm run prepareThis will create the styled-system directory, which is where your generated CSS will end up. You'll need to run this script every time you build and every time you add new tokens or otherwise update the Panda config file.
In order to load in the generated CSS, add an import to styles.css at the root of your app:
import "../styled-system/styles.css";
function App() {
return <div>This is my app</div>;
}This will need to be relative to your App.tsx so do update the path if your file is more or less nested than this.
That should be enough to get Panda CSS up and running! Now you'll want to add some styles.
Styling your components with the css function
Now that Panda CSS is all installed, you can start applying some styles. The simplest way is definitely using the built-in css function. Here's how:
import { css } from "../../styled-system/css";
export const Footer = () => {
return (
<footer
className={css({
borderTop: "1px solid",
borderColor: "secondaryColour",
padding: "1.5rem 0",
fontSize: "0.75rem",
width: "100vw",
textAlign: "center",
})}
>
Any views or opinions expressed...
</footer>
);
};I converted my whole blog to use Panda CSS, so this is the footer that you can see at the bottom of this page. It's a simple React component that outputs a <footer> HTML component - the Panda CSS magic is happening in that call to css(), which you import from the styled-system directory - it turns out that alongside the generated CSS, this directory contains a bunch of helper functions for Panda CSS and this is one of them.
You just pass the css function an object containing your styles and "hey presto!" they get applied. Very neat.
Conditional styles
You might be wondering how you apply things like hover styles in Panda CSS. Well, it turns out there's a notation for that:
<Link
href="/"
className={css({
fontFamily: "specialElite",
color: "foreground",
textDecoration: "none",
fontSize: "1rem",
_hover: {
color: "linkHoverColour",
},
})}
>
Dana's {"{dev}"} adventures
</Link>Here you can see the title of my blog. Note the _hover syntax here - this is like CSS' :hover pseudo class and can be used to control the hover colour of an element.
There are other, similar properties like _active, _first, _even and _odd - read more about them on the Conditional styles page of the Panda docs.
Defining tokens
You may be wondering what that linkHoverColour value means in the example above - that my friend, is a token! Panda CSS doesn't let you use the standard CSS variables (you know, the ones that go var(--name-of-variable)), but you can define your own custom tokens.
This is all done in the Panda config:
import { defineConfig, defineGlobalStyles } from "@pandacss/dev";
export default defineConfig({
theme: {
extend: {
tokens: {
colors: {
rebeccapurple: { value: "#663399" },
neonPink: { value: "#ff00ff" },
},
}
semanticTokens: {
linkHoverColour: {
value: {
base: "{colors.rebeccapurple}",
_dark: "{colors.neonPink}",
},
},
},
},
},
});You can have both primitive tokens (like colors above) and semanticTokens, which use primitive tokens and apply a semantic label to them. It's worth mentioning that Panda CSS comes with some primitives built in, like {colors.white} and {colors.black} exist without you having to define them, which is handy!
These tokens can now be used anywhere you have styles, saving you a lot of typing! It also means that if you change your mind and want to change one of your semantic tokens, the change will instantly apply everywhere.
Light and dark mode
You might have noticed that we have some _dark attributes in our tokens above. This is because Panda CSS lets us define dark mode very easily. Simply set up your semantic tokens as shown above, and then add a "condition" to your Panda config. I've done mine like this:
export default defineConfig({
conditions: {
extend: {
dark: '[data-theme="dark"] &',
},
},
});Though you can use whatever condition you like. In my ThemeToggle component, I simply apply this data attribute to the html tag whenever the toggle is clicked:
const toggleTheme = () => {
document.documentElement.setAttribute("data-theme", newTheme);
};This is what let's Panda CSS automatically switch between the base and _dark versions of the tokens.
How about Global styles?
Most CSS libraries let you create global styles and Panda CSS is no exception. As you might expect, these are also defined in the Panda config file:
export default defineConfig({
globalCss: defineGlobalStyles({
"html, body": {
maxWidth: "100vw",
overflowX: "hidden",
},
html: {
fontSize: "100%",
},
body: {
color: "{colors.foreground}",
backgroundColor: "{colors.background}",
fontFamily: "roboto",
paddingTop: "4rem",
webkitFontSmoothing: "antialiased",
mozOsxFontSmoothing: "grayscale",
},
}),
});We can also use our tokens here for extra code reuse.
Pipelines
Be aware that when you put your website up for folks to look at, you'll need to make sure that your build process runs npm prepare as well as npm build.
My blog runs on Netlify and I was able to go into the deployment settings and add the command in there - hopefully whatever you're using has a similar option!
Advanced concepts
There are some more advanced concepts that are worth looking into if you really want to get into Panda CSS, but that I won't be able to cover in detail in this post (it's already long!).
Shorthand
Panda CSS has several shorthand versions of CSS attributes, for instance:
backgroundColorbecomesbgpaddingbecomespmarginbecomesm
The shorthand properties are documented alongside their longer counterparts so I can't link you to a full list, unfortunately!
Patterns
Panda CSS comes with some built-in "patterns" - these are components that you can use to shorten your styles considerably. Here are some of the key ones:
- Stack - creates a vertical or horizontal stack of items
- Flex - creates a flex container with flexbox
- Grid - creates a grid layout with CSS grid
You can read more in the Panda CSS documentation for patterns.
Recipes
Recipes are incredibly useful for those folks who want to write shareable components - I'm thinking design system authors here. They allow you to define different "variants" for a component and apply different styles depending on those variants. I haven't got any recipes in my blog styles yet, so I'll pull something from the Panda docs instead:
import { cva } from '../styled-system/css'
const button = cva({
base: {
display: 'flex'
},
variants: {
visual: {
solid: { bg: 'red.200', color: 'white' },
outline: { borderWidth: '1px', borderColor: 'red.200' }
},
size: {
sm: { padding: '4', fontSize: '12px' },
lg: { padding: '8', fontSize: '24px' }
}
}
})Recipes are very heavily inspired by the cva (Class Variance Authority) library, which deserves a whole post by itself really.
The above is defining two aspects that can vary for our button component: visual and size. So a <Button /> with visual=solid and size=small will get the following styles applied to it:
background: { colors.red.200 };
color: white;
padding: 4;
font-size: 12px;I had to look up what the padding: 4 meant - by default this translates to 16px but you can customise this in your Panda config.
You can read more in the Panda CSS documentation for recipes.
Should you make the switch?
As always with these things, it depends. If you're running a large Next.js powered website that uses React Server Components and you need a way to keep all your styles consistent and generated at build time, sure, go for it!
For a site like my blog, I'm not sure it's all that useful (except for the learning experience) - it's statically generated and CSS modules work just as well for that, so it's a lot of boilerplate and configuration for not much gain.
I would look at your options and consider what's best in your particular situation, but I must admit my experience using Panda CSS has been positive overall. The css function is easy to use, the config is straightforward to update and I've had fun converting my blog. Make of that what you will!

