2 min read

Panda CSS

I recently started a new web project using Astro.  I initially added Tailwind since it has an official integration, but it's not really my favorite.  There's plenty of debate about mixing CSS and HTML, but I don't actually mind that.  What I'm not a fan of is the utility class soup that Tailwind turns into, especially with pseudo-classes, media classes, etc.

I started mixing in a bunch of handcrafted CSS, but that undercut the value of Tailwind, and I had to configure all my variables in two places.

But then I discovered Panda CSS, and I'm in the process of migrating both the Tailwind and plain CSS classes into Panda.

Panda CSS - Build modern websites using build time and type-safe CSS-in-JS
Build modern websites using build time and type-safe CSS-in-JS

Panda CSS is developed by the same team as Chakra UI, a React-based component library.  I've been using Chakra UI extensively for a few years, so I feel right at home with Panda since it has a lot of similiarities.

Panda is a CSS-in-JS library, but with a build step instead of a runtime.  Styles are defined inline with style objects, and during the build Panda statically analyzes all the files to replace the style objects with generated utility classes.

The basic usage is to define a style object in HTML, something like this:

<div class={css({
    fontSize: '16px',
    color: 'gray'
})}>
    ...
</div>

I'm not using React in Astro, but Astro components support JavaScript frontmatter and JSX-like expressions which get evaluated during the build.  Here's an example component:

---
import { css } from 'styled-system/css';

const { href, ...props } = Astro.props;

const { pathname } = Astro.url;
const subpath = pathname.match(/[^\/]+/g);
const isActive = href === pathname || href === '/' + subpath?.[0];
---

<a
  href={href}
  class={css({
    display: 'flex',
    alignItems: 'center',
    gap: '1rem',
    padding: 'var(--space-3xs) 0',
    '& .active': {
      fontWeight: 'bolder',
      borderTop: '5px solid var(--pink-500)'
    },
    '&:hover': {
      color: 'var(--primary)'
    },
    fontWeight: isActive ? 'bolder' : 'normal'
  })}
  {...props}
>
  <slot />
</a>

The css() function will convert all the properties into bespoke utility classes during the build step.  Panda has built-in theme support, or you can bring your own variables (see above).  Also Patterns and Recipes make it easy to reuse styles across your site.

So it's not that different from Tailwind in the end, but I prefer Panda's developer experience.

After experimenting with various CSS approaches for my new Astro web project, I've settled on Panda CSS as my preferred solution.  I prefer its approach and developer experience over Tailwind, and while I appreciate the simplicity of handwriting CSS, it can get messy and Astro doesn't support dynamic <style> tags.

Overall, Panda CSS strikes a nice balance between the flexibility of utility classes and the usability of CSS-in-JS. Its integration with Astro and focus on type-safe, build-time generation make it a compelling choice for modern web development projects.