Panda CSS vs StyleX
Panda CSSとStyleXを比較。デザイントークン、スタイルの衝突防止、ビルド工程、テーマ、動的な値の違いから、プロジェクトに合う選択肢を見極めます。
Panda CSS と StyleX は、どちらもビルド時にスタイルを静的なアトミック CSS へコンパイルし、スタイリング用のランタイムをブラウザに一切配信しません。両者の違いは、それぞれが解決しようとした問題にあります。Panda はデザインシステムを徹底させるために、StyleX はあるコンポーネントのスタイルが別のコンポーネントのスタイルと衝突するのを防ぐために設計されました。
型付きでコロケーションされたスタイルを手放さずに styled-components や Emotion から移行したいと考えるチームにとって、この 2 つはいずれも有力な候補です。しかも両者の README はほとんど同じことを言っているように見えます。本記事では、両者の唯一の構造的な違いを明らかにし、同じ Button をそれぞれで実装して比較します。そのうえで、この違いがワークフロー、テーマ設定、動的な値にどう影響するかを解説します。より広い視点については、2026 年における CSS-in-JS の現状を参照してください。
重要なポイント
- Panda CSS と StyleX はどちらもビルド時にスタイルをアトミック CSS として抽出するため、レンダリング中にブラウザでスタイルを注入することはありません。
- Panda は
panda.config.ts内のデザイントークンを信頼できる唯一の情報源(Source of Truth)とし、そこから型付きのcss()、cva()、styled()ヘルパーを生成します。 - StyleX では各コンポーネントが
stylex.create()でスタイルを定義し、stylex.props()がそれらをマージします。同じプロパティについては、最後に渡されたスタイルが優先されます。 - どちらのライブラリも、ランタイムにしか存在しない値をアトミッククラスに変換することはできません。StyleX はそうした値を CSS 変数経由で扱い、Panda のドキュメントはレシピ、CSS 変数、data 属性の利用を推奨しています。
- デザインシステムからの逸脱が起きがちなチームには Panda が、スタイルの漏れが繰り返し発生するバグになっている大規模コードベースには StyleX が適しています。ただし、多くのプロジェクトではどちらも必要ありません。
Panda CSS と StyleX の共通点
Panda CSS と StyleX は、どちらもゼロランタイムの CSS-in-JS ライブラリです。TypeScript でスタイルオブジェクトを書くとビルドステップがそれを抽出し、ブラウザには単一目的のアトミッククラスで構成されたプレーンなスタイルシートが届きます。styled-components や Emotion のようなランタイムライブラリは仕組みが異なり、レンダリング中に JavaScript でスタイルを生成・注入します。Panda と StyleX は、こうした処理をすべてコードの配信前に済ませます。同じ対比の旧世代版については、Linaria vs styled-components で解説しています。
出力が静的な CSS であるため、どちらのライブラリもコンテキストプロバイダーやレンダリング時のスタイルインジェクターを必要としません。サーバーでもクライアントでも、レンダリング中にスタイリング関連のコードが実行されないので、どちらも React Server Components で動作します。アトミックな出力では各クラスが 1 つのプロパティだけを設定するため、スタイルシートのサイズはコンポーネント数ではなく、ユニークな宣言の数に比例して増えていきます。
Panda CSS:デザインシステム優先
Panda CSS は、デザインシステムを信頼できる唯一の情報源として扱います。panda.config.ts で定義したトークンをもとにコード生成ステップが実行され、コードベース全体で使う型付きの css()、cva()、styled() ヘルパーが生成されます。Panda は Chakra UI の開発チームによるもので、コンポーネントライブラリ出身であることが設計によく表れています。
panda codegen コマンドを実行すると、プロジェクト内に styled-system/ ディレクトリが書き出されます。コンポーネントはこのディレクトリからヘルパーをインポートし、ディレクトリ内の型はトークンから生成されます。brand がカラートークンであれば、bg: 'brand' が自動補完されます。strictTokens 設定オプションを有効にすると、生の 16 進カラー値は型エラーになります。
レシピは、この仕組みをコンポーネント API へと昇華させます。cva() は base スタイル、名前付きの variants、defaultVariants を受け取り、バリアントの props をクラス名に対応付ける関数を返します。どの選択肢が存在するかはデザインシステムが決め、コンポーネントはその中から選ぶだけです。
StyleX:衝突の防止を優先
StyleX は、スタイルの衝突を中心的な問題として扱います。各コンポーネントは stylex.create() で独自のスタイルオブジェクトを定義し、stylex.props() がそれらを決定論的にマージします。2 つのスタイルが同じプロパティを設定している場合は、最後に渡されたものが優先されます。スタイルシートの記述順やセレクターの詳細度は一切関係しません。StyleX は Meta が公開しているオープンソースで、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 のマージルールはショートハンドにも適用されます。Thinking in StyleX によると、ショートハンドとロングハンドが衝突した場合は常に同じ結果になります。デフォルトでは、どちらを先に渡しても marginTop が margin を上書きします。コンパイルは StyleX の Babel プラグインで行われ、公式のバンドラー統合や PostCSS 統合もこのプラグインの上に構築されています。
同じ Button を両方でスタイリングする
以下の Panda CSS 版と StyleX 版の Button は、どちらも同じ intent prop を公開しています。違うのは、スタイリングの判断がどこに置かれているかです。
// 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)} />
}
Panda 版で bg: 'brand' が型チェックを通るのは、設定に brand が存在し、codegen が対応する型を出力しているからです。StyleX 版では各バリアントが独立したオブジェクトになっており、利用側の style が最後に渡されます。base やバリアントと同じプロパティを設定していれば利用側の値が優先されるため、詳細度と格闘する必要はありません。Panda のヘルパーは開発者が使える「値」を制限し、StyleX のマージルールはあるコンポーネントのスタイルが別のコンポーネントのスタイルを「どう上書きできるか」を制限します。
Panda CSS と StyleX の選択がもたらす実務上の影響
設計思想の違いは、日々の開発における 3 つの領域に表れます。ビルドの仕組み、テーマ設定の仕組み、そしてランタイムにしか存在しない値の扱いです。
| 評価項目 | Panda CSS | StyleX |
|---|---|---|
| ビルドステップ | codegen が styled-system/ を書き出し、PostCSS プラグインまたは CLI が CSS を抽出 | Babel プラグインが stylex.create の呼び出しをコンパイル |
| テーマ設定 | 設定ファイル内のトークン | defineVars と createTheme |
| ランタイムの値 | 抽出不可。レシピ、CSS 変数、data 属性を使用 | CSS 変数経由で処理 |
ワークフロー:Codegen とコンパイラプラグイン
Panda では設定とコンポーネントの間に生成物が挟まるため、トークンを変更したら styled-system/ を再生成する必要があります。StyleX には生成されるディレクトリがありません。Babel プラグインが各モジュールをその場で変換し、インポートや変数の解決方法はプラグインの設定で制御します。
テーマ設定:トークンと変数
Panda ではトークンセットそのものがテーマです。設定内の brand を変更すれば、次回のビルドですべての使用箇所が変わります。StyleX では、テーマ設定はコンポーネント単位のスタイルの上に追加される上書きの仕組みです。stylex.defineVars で .stylex.ts ファイルに変数を宣言し、stylex.createTheme で生成した上書き設定を祖先要素に適用します。
// 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 のテーマ設定 API は、Babel プラグインの unstable_moduleResolution オプションを設定して初めて動作します。
動的な値:ゼロランタイムが苦手とする領域
ビルド時の抽出ツールが認識できるのは、ソースコードに書かれた値だけです。ユーザーが選択した色のように、ランタイムにしかわからない値は、どちらのライブラリでもアトミッククラスにはなりません。Panda の動的スタイリングガイドでは、ソースから読み取れないものは抽出ツールがスキップすると説明されており、レシピ、CSS 変数、data-* 属性の利用が推奨されています。StyleX は動的スタイルを別の方法で扱います。コンパイラは CSS 変数を参照する固定のクラスを出力し、ランタイムの値は要素上のその変数に書き込まれます。
Panda CSS vs StyleX:抱えているのはどちらの問題か
機能一覧ではなく、自分たちが抱えている問題をもとに選びましょう。UI がデザインシステムから逸脱し続けている小規模なチームが抱えているのは、Panda が解決するために作られた問題です。トークンが型になり、デザインシステム外の値はコンパイルが通らなくなります。あるコンポーネントのスタイルが別のコンポーネントに漏れ続けている大規模なコードベースが抱えているのは、StyleX が解決するために作られた問題です。各コンポーネントが自身のスタイルを所有し、呼び出し側でのマージ順序が結果を決めます。
多くのプロジェクトは、どちらの問題も抱えていません。そうしたプロジェクトでは、CSS Modules やユーティリティフレームワークで十分であり、どちらのライブラリよりも少ないビルドツールで済みます。別の 2 つのスタイリング手法の比較については、Tailwind vs Sass を参照してください。
どちらかのライブラリを採用する前に、チームが繰り返し修正しているスタイリングのバグを特定しましょう。それがデザインの逸脱であれば、Panda でレシピを 1 つプロトタイプしてみてください。上書きの衝突であれば、共有コンポーネントを 1 つ StyleX に移植し、利用側でどう振る舞うかを観察してみてください。
よくある質問
Panda CSS と StyleX では、ホバー状態やメディアクエリの書き方はどう違いますか?
StyleX では、疑似クラスやメディアクエリはプロパティの値の中に条件のオブジェクトとして記述し、そのオブジェクトには default エントリが必要です(null を指定することもできます)。例外は ::placeholder などの疑似要素で、これらは名前空間のトップレベルに独立したキーとして記述します。Panda では、bg: { base: 'red.500', _hover: 'red.700' } のようにプロパティ内に条件を書くことも、条件オブジェクトとして書くこともでき、_hover のような組み込みの条件名や、md、lg のようなモバイルファーストのブレークポイントキーを使用します。
StyleX で、Panda の任意セレクターのように子要素をスタイリングできますか?
直接はできません。Panda の css() は '& > *' のような任意セレクターを受け付けますが、StyleX は任意セレクターや離れた要素からのスタイリングを禁止しています。StyleX 0.16.0 では stylex.when 系の API(ancestor、descendant、anySibling、siblingBefore、siblingAfter)が追加されました。ある要素に stylex.defaultMarker() でマーカーを付けておくと、その要素がホバーやフォーカスされたとき、あるいは属性に一致したときに、別の要素のスタイルを変更できます。descendant、anySibling、siblingAfter の各バリアントは CSS の :has() セレクターに依存しています。また、親要素で defineVars の変数を設定する方法も、ドキュメントで紹介されている手段の 1 つです。
stylex.props() はブラウザで実行されますか?
場合によっては実行されますが、それは小さなマージ処理に限られます。StyleX のコンパイラはビルド時に stylex.create() の呼び出しを静的なアトミック CSS に変換し、スタイルの作成と使用が同じファイル内で完結している場合は stylex.props() の呼び出し自体を除去できます。スタイルがファイルをまたぐ場合、stylex.props() はコンパイル済みのスタイルオブジェクトを左から右へマージし、React 用の className と style の props を返す関数として実行されます。ブラウザで CSS を生成したり注入したりすることは一切ありません。Solid、Svelte、Qwik、Vue などの React 以外のフレームワーク向けには、stylex.attrs() も用意されています。
Panda CSS の strictTokens と strictPropertyValues の違いは何ですか?
strictTokens は、color、bg、borderColor など、トークンに裏付けられたプロパティを対象とします。有効にすると、これらのプロパティにはトークンの値しか指定できなくなります。strictPropertyValues は、display、position、overflow など、既知のキーワード値を持つ特定のプロパティ群を対象とします。有効にすると、値がそれらのキーワードのいずれでもない場合に Panda がエラーをスローします。どちらも panda.config.ts で有効化します。いずれの場合も、[123px] のように生の値を角括弧で囲めば、TypeScript のエラーを出さずに使用できます。
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