12k
All articles

Panda CSS vs StyleX

Compare Panda CSS and StyleX: see how design tokens, style collision prevention, build workflows, theming, and dynamic values shape the right choice for your project.

OpenReplay Team
OpenReplay Team
Panda CSS vs StyleX

Panda CSS and StyleX both compile styles to static atomic CSS at build time and ship no styling runtime to the browser. What separates them is the problem each was designed to solve: Panda enforces a design system, and StyleX prevents one component’s styles from colliding with another’s.

Both show up on the shortlist when a team wants to move off styled-components or Emotion without giving up typed, colocated styles, and the two READMEs sound almost the same. This article sets out the one structural difference between them, shows the same Button written in each, and traces how that difference affects workflow, theming and dynamic values. For the wider picture, see the state of CSS-in-JS in 2026.

Key Takeaways

  • Panda CSS and StyleX both extract styles at build time into atomic CSS, so neither injects styles in the browser during render.
  • Panda makes design tokens in panda.config.ts the source of truth and generates typed css(), cva() and styled() helpers from them.
  • StyleX has each component define styles with stylex.create(), and stylex.props() merges them so that the last style passed wins for a given property.
  • Neither library can turn a value that only exists at runtime into an atomic class. StyleX routes such values through CSS variables, and Panda’s docs point you to recipes, CSS variables or data attributes.
  • Panda fits a team drifting from its design system, StyleX fits a large codebase where style leakage is the recurring bug, and many projects need neither.

What Panda CSS and StyleX Share

Panda CSS and StyleX are both zero-runtime CSS-in-JS libraries. You write style objects in TypeScript, a build step extracts them, and the browser gets a plain stylesheet made of single-purpose atomic classes. Runtime libraries such as styled-components and Emotion work differently: they generate and inject styles via JavaScript during render. Panda and StyleX do all of that work before the code ships. If you want the older version of the same split, Linaria vs styled-components covers it.

Because the output is static CSS, neither library needs a context provider or a render-time style injector. That is why both work with React Server Components: no styling code runs during a server or client render. In atomic output, each class sets one property, so stylesheet size grows with the number of unique declarations rather than with the number of components.

Panda CSS: Design System First

Panda CSS treats the design system as the source of truth. Tokens defined in panda.config.ts drive a code-generation step, which produces typed css(), cva() and styled() helpers for the rest of the codebase. Panda comes from the team behind Chakra UI, and the component-library background shows.

Running the panda codegen command writes a styled-system/ directory into your project. Components import their helpers from that directory, and the types in it are generated from your tokens. If brand is a colour token, bg: 'brand' autocompletes. With the strictTokens config option, a raw hex value becomes a type error.

Recipes turn this into component APIs. cva() takes a base style, named variants and defaultVariants, and returns a function that maps variant props to class names. The design system decides which options exist, and components pick from that list.

StyleX: Collisions First

StyleX treats style collisions as the core problem. Each component defines its own style objects with stylex.create(), and stylex.props() merges them deterministically, so that when two styles set the same property, the last one passed wins. Stylesheet order and selector specificity play no part. StyleX is open source from Meta, and it is the styling layer under design systems such as Astryx.

const styles = stylex.create({
  a: { color: 'red' },
  b: { color: 'blue' },
})
stylex.props(styles.a, styles.b) // blue
stylex.props(styles.b, styles.a) // red

StyleX’s merge rules also cover shorthands. Thinking in StyleX explains that a clash between a shorthand and a longhand always resolves the same way: out of the box, marginTop overrides margin no matter which one you pass first. Compilation happens in StyleX’s Babel plugin, and the official bundler and PostCSS integrations are built on top of it.

The Same Button Styled Both Ways

The Panda CSS and StyleX Buttons below expose the same intent prop. The difference is where the styling decisions live.

// Panda: panda.config.ts defines theme.extend.tokens.colors.brand = { value: '#2563eb' }
import { cva } from '../styled-system/css'

const button = cva({
  base: { px: '4', py: '2', borderRadius: 'md', fontWeight: 'semibold' },
  variants: {
    intent: {
      primary: { bg: 'brand', color: 'white' },
      ghost: { bg: 'transparent', color: 'brand' },
    },
  },
  defaultVariants: { intent: 'primary' },
})

export function Button({ intent, ...props }) {
  return <button className={button({ intent })} {...props} />
}
// StyleX
import * as stylex from '@stylexjs/stylex'

const styles = stylex.create({
  base: { paddingInline: 16, paddingBlock: 8, borderRadius: 6, fontWeight: 600 },
  primary: { backgroundColor: '#2563eb', color: 'white' },
  ghost: { backgroundColor: 'transparent', color: '#2563eb' },
})

export function Button({ intent = 'primary', style, ...props }) {
  return <button {...props} {...stylex.props(styles.base, styles[intent], style)} />
}

In the Panda version, bg: 'brand' only type-checks because brand exists in the config and codegen has emitted a matching type. In the StyleX version, each variant is its own object, and the consumer’s style is passed last. Wherever it sets the same property as the base or the variant, the consumer’s value wins, and there is no specificity to fight. Panda’s helpers limit which values a developer can use. StyleX’s merge rule limits how one component’s styles can override another’s.

Practical Consequences of Choosing Panda CSS or StyleX

The difference in origin shows up in three day-to-day areas: how the build works, how theming works, and what happens with values that only exist at runtime.

CriterionPanda CSSStyleX
Build stepCodegen writes styled-system/; PostCSS plugin or CLI extracts CSSBabel plugin compiles stylex.create calls
ThemingTokens in configdefineVars plus createTheme
Runtime valuesNot extractable; use recipes, CSS variables, data attributesRouted through CSS variables

Workflow: Codegen vs Compiler Plugin

In Panda, a generated artifact sits between your config and your components, so changing a token means regenerating styled-system/. StyleX has no generated directory. The Babel plugin transforms each module where it is written, and the plugin’s configuration controls how imports and variables resolve.

Theming: Tokens vs Variables

In Panda, the theme is the token set: change brand in the config and every usage changes on the next build. In StyleX, theming is an override mechanism added on top of per-component styles. stylex.defineVars declares variables in a .stylex.ts file, and stylex.createTheme produces overrides that you apply to an ancestor element:

// tokens.stylex.ts
import * as stylex from '@stylexjs/stylex'
export const colors = stylex.defineVars({ brand: '#2563eb' })

// app.tsx
import * as stylex from '@stylexjs/stylex'
import { colors } from './tokens.stylex'

const dark = stylex.createTheme(colors, { brand: '#60a5fa' })
<div {...stylex.props(dark)}>{children}</div>

StyleX’s theming APIs only work once the Babel plugin’s unstable_moduleResolution option is configured.

Dynamic Values: Where Zero-Runtime Gets Awkward

A build-time extractor only sees values written in the source. A value that is only known at runtime, such as a user-selected colour, cannot become an atomic class in either library. Panda’s dynamic styling guide explains that its extractor skips anything it can’t read from the source, and steers you towards recipes, CSS variables or data-* attributes. StyleX handles dynamic styles differently: the compiler emits a fixed class that points at a CSS variable, and the runtime value is written to that variable on the element.

Panda CSS vs StyleX: Which Problem Do You Have?

Choose based on the problem you have, not on the feature list. A small team whose UI keeps drifting from its design system has the problem Panda was built for: tokens become types, and off-system values stop compiling. A large codebase where one component’s styles keep leaking into another has the problem StyleX was built for: every component owns its styles, and the merge order at the call site decides the result.

Many projects have neither problem. For them, CSS Modules or a utility framework does the job with less build tooling than either library needs. For a comparison of two other styling approaches, see Tailwind vs Sass.

Before you commit to either library, identify the styling bug your team keeps fixing. If it is design drift, prototype one recipe in Panda. If it is override conflicts, port one shared component to StyleX and watch how its consumers behave.

FAQs

How do hover states and media queries differ between Panda CSS and StyleX?

In StyleX, pseudo-classes and media queries go inside a property's value as an object of conditions, and that object needs a default entry, which you can set to null. Pseudo-elements such as ::placeholder are the exception: they are written as their own key at the top level of the namespace. Panda accepts conditions inside a property, as in bg: { base: 'red.500', _hover: 'red.700' }, or as condition objects, using built-in names like _hover and mobile-first breakpoint keys like md and lg.

Can StyleX style child elements the way Panda's arbitrary selectors can?

Not directly. StyleX disallows arbitrary selectors and styling at a distance, while Panda's css() accepts arbitrary selectors such as '& > *'. StyleX 0.16.0 added the stylex.when APIs (ancestor, descendant, anySibling, siblingBefore and siblingAfter). You tag one element with stylex.defaultMarker(), and another element changes its styles when the tagged element is hovered, focused or matches an attribute. The descendant, anySibling and siblingAfter variants depend on the CSS :has() selector. Setting a defineVars variable on a parent is another documented route.

Does stylex.props() run in the browser?

Sometimes, and only as a small merge step. The StyleX compiler turns stylex.create() calls into static atomic CSS at build time, and when styles are created and used in the same file it can remove the stylex.props() call entirely. When styles cross file boundaries, stylex.props() runs as a function that merges compiled style objects from left to right and returns className and style props for React. It never generates or injects CSS in the browser. For non-React frameworks such as Solid, Svelte, Qwik and Vue, StyleX also provides stylex.attrs().

What is the difference between strictTokens and strictPropertyValues in Panda CSS?

strictTokens is about properties backed by tokens, such as color, bg and borderColor: with it on, only token values are accepted there. strictPropertyValues is about a fixed list of properties with known keyword values, such as display, position and overflow: with it on, Panda throws when a value is not one of those keywords. You turn on both in panda.config.ts. Either way, wrapping a raw value in square brackets, as in [123px], lets it through without a TypeScript error.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.