Migrating from StyleX
Migrate your project from Meta's StyleX to Panda and see how create, defineVars, and props map across.
This guide outlines the steps needed to migrate your project from StyleX to Panda and highlights key design differences between the two.
Disclaimer: This isn't about which one is best. Both compile to static CSS at build time, so the differences below are about strictness and scope, not runtime cost.
Here are some similarities between the two:
- Both extract to static CSS at build time, no runtime style computation in either.
- Both use object syntax for styles rather than tagged template strings.
- Both define theme values in a central place and reference them by name in style objects.
The foundation is the same, so the decision is about scope. StyleX stays deliberately minimal, single-element styles,
no globals, no built-in variants, which keeps specificity predictable but leaves variants and layout for you to build.
Panda ships that layer, recipes, patterns, and semantic tokens as typed APIs, on the same static-CSS output. Reach for
Panda when you want the design system in the box; stay on StyleX if strict per-element scoping is the point. The rest of
this guide maps defineVars / create / props to Panda's equivalents, and flags the constraints that don't carry
over.
How styles compile
Both are already build-time, static systems, so unlike a migration from Emotion or Chakra, this isn't a runtime-vs-static story. StyleX's atomic CSS model is deliberately strict about style precedence (the last property wins, resolved partly through property order at the call site) and requires a compiler plugin (Babel, SWC, or the Rust-based one) wired into your build. Panda extracts with its own Rust engine built on Oxc (opens in a new tab), native for your build and WebAssembly in the browser. Structurally the same, static extraction plus atomic output, but it doesn't require you to reason about call-site property order the way StyleX does.
Theming
StyleX defines theme values with stylex.defineVars, which compiles to real CSS custom properties. The call has to
live in a file with a .stylex.js/.stylex.ts extension and be a named export, StyleX's compiler enforces this:
vars.stylex.ts
import * as stylex from '@stylexjs/stylex'
export const colors = stylex.defineVars({
brand: '#0ea5e9'
})const styles = stylex.create({
button: { backgroundColor: colors.brand }
})
Panda's theme tokens live in panda.config.ts instead of a separate defineVars call, and are referenced by name
as string values rather than imported variable objects:
panda.config.ts
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
theme: {
extend: {
tokens: {
colors: {
brand: { value: '#0ea5e9' }
}
}
}
}
})import { css } from '../styled-system/css'
<button className={css({ bg: 'brand' })} />
See Tokens.
The sx prop
StyleX has no inline-prop escape hatch, every element applies its styles explicitly through stylex.props(),
spread onto the element:
import * as stylex from '@stylexjs/stylex'
const styles = stylex.create({
base: { color: 'white', backgroundColor: 'blue' }
})
<div {...stylex.props(styles.base)} />
Panda's css() is closer to this than a dedicated sx prop, you build the class name and pass it to className
directly, or use the styled factory's css prop for the same one-off styling on top of an existing component:
import { css } from '../styled-system/css'
<div className={css({ color: 'white', bg: 'blue' })} />
See JSX Style Props.
Variants
StyleX has no built-in variant system, the common pattern is a stylex.create call with multiple named style
objects, and picking one at render time based on a prop:
const styles = stylex.create({
primary: { backgroundColor: 'blue' },
secondary: { backgroundColor: 'gray' }
})
<button {...stylex.props(styles[variant])} />
Panda's recipes formalize this as a typed variant map, the prop and its allowed values are checked at compile time instead of being an untyped string key lookup:
button.ts
import { cva } from '../styled-system/css'
export const button = cva({
variants: {
variant: {
primary: { bg: 'blue.500' },
secondary: { bg: 'gray.500' }
}
}
})<button className={button({ variant: 'primary' })}>Click me</button>
See Recipes and Slot Recipes for multi-part components.
Color Modes
StyleX handles color mode inside defineVars itself, keying a value to a media query:
const DARK = '@media (prefers-color-scheme: dark)'
export const colors = stylex.defineVars({
bg: { default: 'white', [DARK]: 'black' }
})
Panda's semantic tokens are the direct equivalent, a token whose value branches on the _dark condition instead of
an inline media-query key:
panda.config.ts
theme: {
extend: {
semanticTokens: {
colors: {
bg: { value: { base: 'white', _dark: 'black' } }
}
}
}
}<div className={css({ bg: 'bg' })} />
See Theme and Multiple Themes for color-mode setup beyond media-query-only switching (a manually toggled class or attribute, for example).
Global Styles
This is the one place StyleX and Panda genuinely disagree, not just in syntax. StyleX deliberately has no global
styles and no descendant or nested selectors targeting other elements, styles on an element have to come from class
names on that element itself. The only exception is ordinary CSS inheritance (a color cascading to children the
normal CSS way), and StyleX offers a separate stylex.when API for the controlled cases where you do need one
element's state to affect another, rather than reaching for an implicit descendant selector. This is a core design
constraint, not a missing feature, it's what lets StyleX guarantee predictable specificity at scale.
Panda does support global styles, through globalCss in panda.config.ts:
panda.config.ts
export default defineConfig({
globalCss: {
body: { margin: 0 }
}
})See Global Styles. If you were relying on StyleX's strict per-element scoping as a
guardrail against accidental global overrides, note that Panda doesn't enforce that for you, it's on your team to
avoid reaching for globalCss or descendant selectors where a scoped style would do.
Component Styles
StyleX has no built-in layout primitives (no Box, Stack, Grid), consistent with its low-level, single-element
scope. Panda ships patterns for common layout shapes, as both a function and a JSX
component:
import { Box, Stack } from '../styled-system/jsx'
<Stack gap="4">
<Box bg="gray.100">Item</Box>
</Stack>
Conclusion
StyleX and Panda share the same foundation, static extraction, atomic output, no runtime cost, but StyleX adds real
constraints (no global styles, no descendant selectors, strict call-site property ordering) that Panda doesn't
enforce. Moving from StyleX to Panda mostly means translating defineVars/create/props to Panda's config and
css()/cva() equivalents, and deciding deliberately, since Panda won't enforce it for you, whether to keep
StyleX's scoping discipline in your own styles.
See also
- Migration strategy for running both side by side during the migration.
- Cascade Layers for how Panda manages selector precedence, the nearest equivalent to StyleX's specificity guarantees.