StyleXはMetaが2023年12月5日にnpmで公開したスタイリングライブラリで、JavaScriptで書いたスタイルをビルド時にアトミックなCSSファイルへ変換します。2026年10月時点の最新版は2026年9月15日公開の0.19.1です。日本語の解説は公開直後の2023〜2024年のものが多く、非推奨になったNext.js用プラグインや、npm install stylexという無関係な別パッケージの手順が残っています。ここでは0.19.1を実際にコンパイルした出力と公式ドキュメントを根拠に、導入設定・書き方・型での制約・見送る条件を整理します。
まとめ
- StyleXはBabelプラグインがビルド時にスタイルを抜き出し、静的なCSSを書き出す。実行時にstyleタグを生成しない。
- 入れるパッケージは
@stylexjs/stylex。スコープなしのstylexは別作者の無関係なパッケージで、旧記事が使う@stylexjs/nextjs-pluginは0.12.0(2025年4月)で非推奨になった。 - Next.jsはBabel+PostCSS、Vite・Webpack・Rspackは
@stylexjs/unpluginで設定する。 - スタイルは
stylex.createで定義しstylex.propsで適用する。同じプロパティは最後に渡した側が勝ち、0.19.1の実出力でも負けた側のクラスはclass属性から消える。 - コンポーネントが受け取るスタイルは
StyleXStylesの型引数で絞れる。0.19.1ではenableDebugClassNamesが削除され、開発時もクラス名は本番と同じハッシュになった。
ビルド時にCSSを書き出すStyleXの仕組み
アトミックCSSへの変換とMeta社内の80%削減
StyleXのBabelプラグインは、ファイル内のstylex.create呼び出しを静的に解析し、「プロパティと値の組み合わせ」ごとに1つのクラスを作ります。color: 'red'を100か所に書いても、出力されるCSSルールは1つです。Metaのエンジニアリングブログ「StyleX: A Styling Library for CSS at Scale」(2025年11月11日)は、Facebook.comの作り直しでアトミックなクラスによってCSSサイズが80%減ったと書いています。同社のコードベースでの値です。
同じ記事によれば、StyleXはFacebook・Instagram・WhatsApp・Messenger・Threadsで標準のスタイリングシステムになっており、社外ではFigmaとSnowflakeが採用しています。npm Downloads APIで見た週間ダウンロード数(2026年10月4〜10日)は約194万回で、styled-components(約1,103万回)の6分の1程度です。
0.19.1のコンパイラが実際に出力するCSS
挙動を確かめるため、@stylexjs/babel-plugin 0.19.1と@babel/coreで次のコンポーネントをコンパイルしました(dev: false、runtimeInjection: false)。
const styles = stylex.create({
base: { fontSize: 16, color: 'rgb(60,60,60)', padding: 8 },
button: {
backgroundColor: { default: 'lightblue', ':hover': 'blue' },
width: { default: 800, '@media (max-width: 800px)': '100%' },
},
});
JavaScript側のstylesは、プロパティごとのキーにクラス名を対応させただけのオブジェクトに置き換わります(base: { kGuDYH: "x1j61zf2", kMwMTN: "x10ipcqr", kmVPX3: "xe8ttls", $$css: true })。CSSはprocessStylexRulesで1ファイルにまとめられ、useLayers: trueを指定すると次の形になりました(後述のhighlightedのルールは省略)。
@layer priority1, priority2, priority3;
@layer priority1{
.xe8ttls{padding:8px}
}
@layer priority2{
.x15neal0{background-color:lightblue}
.x10ipcqr{color:rgb(60,60,60)}
.x1j61zf2{font-size:16px}
.xbrh7vm:hover{background-color:blue}
}
@layer priority3{
.xdpfuu1{width:800px}
@media (max-width: 800px){.xkf749i.xkf749i{width:100%}}
}
ショートハンドのpaddingは個別プロパティより低いレイヤーに置かれるので、paddingとpaddingTopを併用しても記述順に関係なくpaddingTopが勝ちます。useLayersを省略した場合(Babelプラグインの設定リファレンスでは既定値false)は@layerを使わず、paddingは素の.xe8ttls、colorは.x10ipcqr:not(#\#)、メディアクエリ付きのwidthは:not(#\#)を2つ重ねる、というように付ける数で同じ優先順位を表していました。レイヤー番号や重ねる数は、そのビルドに含まれるスタイル全体で使われた優先度に応じて振り直されます。:not(#\#)は1つでIDセレクタ1個分の詳細度を足すため、既存CSSのクラスセレクタよりStyleX側が勝ちやすくなります。既存のCSSと混ぜるならuseLayersを有効にし、レイヤーの順序で関係を決める方が管理しやすくなります。
実行時CSS-in-JSとの構造的な違い
Emotionやstyled-componentsのような従来型のCSS-in-JSは、レンダリング中にスタイル文字列を組み立ててstyleタグをDOMに挿入します。Metaのブログは、この方式が「計測できるほどの性能上のオーバーヘッド」を持ち込んでいたと振り返っています。StyleXでは実行時に行うのはクラス名の結合だけで、その代わりスタイルの値は静的に解析できる形で書く必要があります(任意の関数の戻り値やオブジェクトのスプレッドは不可)。
StyleXのインストールとビルド設定
正しいパッケージは@stylexjs/stylex(stylexは別物)
ランタイムは@stylexjs/stylexです。npmでスコープなしのstylexを引くと、説明文「Easily convert CSS styles between the DOM and Javascript, and back.」、メンテナーryanhefner、最新版0.3.0の別パッケージが出てきます。Metaのライブラリとは関係がないため、npm install stylexと書かれた手順は誤りです。
npm install --save @stylexjs/stylex
npm install --save-dev @stylexjs/babel-plugin @stylexjs/postcss-plugin postcss autoprefixer
npm install --save-dev @stylexjs/eslint-plugin
ESLintプラグインは省きたくなりますが、公式のインストール手順は「StyleXのコンパイラは寛容で、誤りを含むスタイルもコンパイルしてしまう」として導入を勧めています。ゼロから作るなら、0.18.0で入ったスキャフォールドCLI(npm create @stylexjs my-app)でテンプレートを選んで雛形を作れます。
2024年以前の手順で使う@stylexjs/nextjs-pluginは非推奨
公開直後の解説記事の多くは、@stylexjs/nextjs-pluginでnext.config.jsを包む手順です。CHANGELOGによると、このパッケージは0.12.0(npm公開2025年4月11日)で@stylexjs/webpack-plugin・@stylexjs/esbuild-plugin・@stylexjs/dev-runtime・@stylexjs/open-propsとともに非推奨になりました。npmレジストリ上も最終版は0.11.1(2025年3月3日)で止まり、「Package no longer supported.」の非推奨表示が付いています。
旧記事のgenConditionalClassesも0.13.0でenableInlinedConditionalMergeに改名されています。古い設定は写さず、次の節の構成で組み直してください。
Next.jsのBabel・PostCSSによる2段構成
現行のNext.js向け手順では、BabelプラグインがStyleX呼び出しを変換し、PostCSSプラグインが@stylexディレクティブを生成済みCSSに置き換えます。
// babel.config.js
const path = require('path');
const dev = process.env.NODE_ENV !== 'production';
module.exports = {
presets: ['next/babel'],
plugins: [
['@stylexjs/babel-plugin', {
dev,
runtimeInjection: false,
enableInlinedConditionalMerge: true,
treeshakeCompensation: true,
aliases: { '@/*': [path.join(__dirname, '*')] },
unstable_moduleResolution: { type: 'commonJS' },
}],
],
};
// postcss.config.js
const babelConfig = require('./babel.config');
module.exports = {
plugins: {
'@stylexjs/postcss-plugin': {
include: ['src/**/*.{js,jsx,ts,tsx}', 'pages/**/*.{js,jsx,ts,tsx}', 'app/**/*.{js,jsx,ts,tsx}', 'components/**/*.{js,jsx,ts,tsx}'],
babelConfig: {
babelrc: false,
parserOpts: { plugins: ['typescript', 'jsx'] },
plugins: babelConfig.plugins,
},
useCSSLayers: true,
},
autoprefixer: {},
},
};
ルートレイアウトで読み込むCSS(app/globals.cssなど)に@stylex;を1行だけ書くと、そこにアプリ全体分のCSSが出力されます。PostCSS側がBabel設定を読み込むのは両者の設定を一致させるためで、公式ドキュメントは「Next.js 16.0.3以降はWebpackとTurbopackの両方で動作する」としており、next.config.jsの変更は要りません。
Babel設定を置いたときの影響はバンドラーで異なります。Next.js Compilerのドキュメントのとおり、WebpackではRust製のSWCをやめてBabelで変換します。Turbopackのドキュメントによれば、Next.js 16以降のTurbopackは設定ファイルを検出するとBabelを自動で使いつつ、Next.js内部の変換と古いECMAScript向けの変換にはSWCを使い続けます(node_modules内のファイルはBabelの対象外)。どちらの場合も、大きなアプリではビルド時間を導入前後で測ってください。
Vite・Webpack・Rspackのunplugin設定
0.17.0(2025年11月)で@stylexjs/unpluginが入り、Vite・Webpack・Rspack・Rollupなどを1つのパッケージで扱えるようになりました。依存に@babel/coreを含んでおり、Babelを内蔵しなくなった@vitejs/plugin-react v6と組み合わせても、StyleXの変換は自前で行います。
npm install --save-dev @stylexjs/unplugin
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import stylex from '@stylexjs/unplugin';
export default defineConfig({
plugins: [
stylex.vite({ useCSSLayers: true }),
react(),
],
});
Vite + Reactの手順は、Fast Refreshを保つためにStyleXのプラグインを@vitejs/plugin-reactより前に置くよう指示しています。vite buildではアプリのルートで読み込んだCSSファイルにStyleXの出力が追記されるので、CSSのリクエストは増えません。
StyleXの主要APIとスタイル記法
createとpropsによる定義と適用、後勝ちのマージ
中核は2つの関数です。stylex.createで名前付きのスタイルを定義し、stylex.propsの戻り値を要素にスプレッドします。className={styles.button}のようにスタイルオブジェクトを直接渡す書き方はできません。
import * as stylex from '@stylexjs/stylex';
const styles = stylex.create({
base: { fontSize: 16, color: 'rgb(60,60,60)', padding: 8 },
highlighted: { color: 'rebeccapurple' },
});
function Label({ isHighlighted }) {
return <span {...stylex.props(styles.base, isHighlighted && styles.highlighted)}>StyleX</span>;
}
同じプロパティが重なったら、propsに後から渡した方が勝ちます。0.19.1のランタイムで実行すると、props(base, highlighted)の戻り値のclassNameは"x1j61zf2 xe8ttls x1n0khkq"になり、base側のcolorクラスx10ipcqrはclass属性に残りませんでした。順序を入れ替えたprops(highlighted, base)ではx10ipcqrが残ります。CSS側の詳細度で競り合う前に、ランタイムがクラスを1つに絞っているわけです。null・undefined・falseは無視されるため、&&や三項演算子で条件分岐を書けます。
擬似クラス・メディアクエリのオブジェクト値記法
擬似クラスやメディアクエリは別のスタイルに分けず、プロパティの値をオブジェクトに置き換えて書きます。defaultキーが通常時の値です。
const styles = stylex.create({
button: {
backgroundColor: { default: 'lightblue', ':hover': 'blue', ':active': 'darkblue' },
width: { default: 800, '@media (max-width: 800px)': '100%' },
},
});
擬似要素は逆に、名前空間の直下に'::placeholder': { color: '#999' }のようなキーで書きます。スタイル定義のドキュメントは、CSSを小さく保つために::beforeと::afterは避け、実際のHTML要素に置き換えるよう勧めています。
アロー関数とCSS変数による実行時の値の指定
実行時に決まる値は、名前空間をアロー関数として定義します。
const styles = stylex.create({
dyn: (h) => ({ height: h }),
});
<div {...stylex.props(styles.dyn(120))} />
0.19.1でコンパイルすると、生成されるCSSは.x16ye13r{height:var(--x-height)}という固定のクラス1つと、@property --x-height { syntax: "*"; inherits: false;}だけでした。値そのものはインラインstyleの--x-heightに入り、数値なら120pxのようにpxを付けて渡されます。値の種類が何通りあってもCSSは増えない代わりに、要素ごとにstyle属性が付きます。
sxプロパティと@stylexjs/atomsのインライン指定
1か所でしか使わないスタイルに名前を付けるのが面倒なら、0.18.1で入ったsxプロパティと、0.19.0で入った@stylexjs/atomsが使えます。sx={...}は{...stylex.props(...)}の短縮記法で、使えるのはdivやbuttonのような小文字のDOM要素だけです。
import * as stylex from '@stylexjs/stylex';
import x from '@stylexjs/atoms';
<button {...stylex.props(x.display['inline-flex'], x.borderRadius._6px, x.color(color))} />
静的なアトムはプロパティアクセス、動的なアトムは関数呼び出しで書き、数字で始まる値には_6pxのように先頭へアンダースコアを付けます。atomsのドキュメントによれば、atomsはBabelプラグインでコンパイルされる前提で、コンパイルされないままアクセスすると実行時に例外を投げます。
stylex.whenによる祖先・兄弟の状態の観測
StyleXは、離れた場所から別の要素を装飾するセレクタを禁じています。Metaのブログはその直後に「ただしstylex.when APIによる離れた場所からの観測は許可している」と例外を書いています。0.16.0で入った機能です。
const styles = stylex.create({
foo: {
backgroundColor: { default: 'blue', [stylex.when.ancestor(':hover')]: 'red' },
},
});
<div {...stylex.props(stylex.defaultMarker())}>
<div {...stylex.props(styles.foo)}>Some content</div>
</div>;
観測される側にstylex.defaultMarker()を付け、観測する側が条件を書きます。0.18.0からは[data-state="open"]のような属性セレクタも条件にできます。whenのドキュメントは、siblingAfter・anySibling・descendantがCSSの:has()に依存すると注記しています。MDNの互換性表ではChrome 105・Safari 15.4・Firefox 121で対応済みなので、対象ブラウザの下限がこれより古いときだけ避ければ足ります。
defineVarsとcreateThemeによるテーマ変数の管理
デザイントークンはstylex.defineVarsでCSSカスタムプロパティとして定義します。定義できるのは.stylex.js(または.stylex.ts)という拡張子のファイルの名前付きエクスポートだけで、この制約があるので他のモジュールからimportした変数もビルド時に解決できます。
// tokens.stylex.js
import * as stylex from '@stylexjs/stylex';
export const colors = stylex.defineVars({
accent: 'blue',
'--background': 'white',
});
0.19.1でのコンパイル結果は:root, .xilfsc6{--xdhl4a2:blue;--background:white;}でした。--で始まるキーだけが名前をそのまま保ち、それ以外はハッシュ化された変数名になります。テーマの差し替えはstylex.createThemeにdefineVarsの戻り値と上書き値を渡します。0.18.0のstylex.envはビルド設定からトークンを配れますが、公式は実験的なAPIと明記しています。
非React環境・HTML出力向けのstylex.attrs
stylex.propsが返すのはReact向けのclassNameとstyleオブジェクトです。class属性と文字列のstyle属性を期待する環境ではstylex.attrsを使います。0.13.0でいったん削除され、0.18.2で復活したAPIで、0.19.1ではattrs(base, highlighted)が{"class":"x1j61zf2 xe8ttls x1n0khkq"}を返しました。attrsのドキュメントは、SSRの出力とSolid・Svelte・Vue・Qwikを想定用途に挙げています。
StyleXStylesによるコンポーネントのスタイル制約
StyleXStyles<{…}>によるプロパティと値の制限
共通コンポーネントにstyleプロパティを開けると、利用側が何でも上書きできてしまいます。静的型のドキュメントにあるStyleXStylesの型引数を使うと、受け付けるプロパティと値を型で限定できます。
import * as stylex from '@stylexjs/stylex';
import type { StyleXStyles } from '@stylexjs/stylex';
type Props = { style?: StyleXStyles<{ marginTop?: 0 | 4 | 8 | 16 }> };
function Box({ style }: Props) {
return <div {...stylex.props(style)} />;
}
const s = stylex.create({ ok: { marginTop: 8 }, bad: { marginTop: 12 } });
<Box style={s.ok} />; // 通る
<Box style={s.bad} />; // TS2322(Type '12' is not assignable to ...)
TypeScript 7.0.2で確かめたところ、marginTop: 12と、許可していないcolorを渡した場合はどちらもTS2322になりました。動的スタイルまで拒みたい場合はStaticStylesを使います。公式は「TypeScriptのオブジェクト型は余分なプロパティでエラーにならないため、許可していないスタイルが通るケースが残りうる」とも注記しているので、型だけで完全に防げるとは考えない方が安全です。
StyleXStylesWithoutの公式例におけるstrict時のTS2344
逆に「レイアウト系だけ禁止して、色などは自由にさせたい」ときはStyleXStylesWithoutを使います。ここで1点つまずきやすいのが、公式ドキュメントの例が禁止するキーの型をunknownで書いていることです。
import type { StyleXStylesWithout } from '@stylexjs/stylex';
// 公式例と同じ書き方
type NoLayout = StyleXStylesWithout<{ width: unknown; margin: unknown }>;
// TS2344: Type '{ width: unknown; margin: unknown; }' does not satisfy the constraint 'UserAuthoredStyles'.
// strict でも通る書き方
type NoLayout2 = StyleXStylesWithout<{ width: string | number; margin: string | number }>;
手元ではTypeScript 5.9.3の--strictなしではエラーにならず、--strictを付けるとTS2344が出ました。TypeScript 7.0.2ではオプションを付けなくても同じエラーになります。unknownをstring | numberに変えると制約エラーは消え、禁止したwidthを渡したときのTS2322は残り、colorは通りました。プロパティを禁止する効果はどちらの書き方でも変わりません。
Tailwind CSS・styled-componentsとの使い分け
3つとも「CSSファイルを直接書かない」点は同じですが、スタイルが確定する時点と、競合の解決方法が違います。
| 観点 | StyleX | Tailwind CSS | styled-components |
|---|---|---|---|
| スタイルの確定 | ビルド時 | ビルド時 | 実行時に注入 |
| 書く場所 | JS/TSのオブジェクト | classの文字列 | テンプレート内のCSS |
| 型チェック | 値まで検査 | クラス名は文字列 | CSS文字列は対象外 |
| 競合の解決 | props引数の後勝ち | 生成CSSの順序 | 詳細度とCSS順序 |
| グローバルセレクタ | 不可(whenで観測) | 可 | createGlobalStyleで可 |
| ビルド工程 | 必須 | 必須 | 不要 |
| 週間DL(2026年10月4〜10日) | 約194万 | 約1億3,623万 | 約1,103万 |
クラス名で画面を素早く組むならTailwind CSS v4が有利です。共通コンポーネントを複数のアプリへ配り、利用側の上書き範囲と順序を保証したいなら、後勝ちが仕様で型で受け口を絞れるStyleXが向きます。ビルド時生成でバリアントをレシピで束ねる方式なら、Panda CSSの設定と採用判断も並べて検討してください。複数のWebアプリでデザインを揃える設計から相談したい場合は、業務用・Webアプリ開発の支援も行っています。
なおstyled-componentsは「2025年3月にメンテナンスモードになった」と説明されがちですが、6.3.0(2026年1月10日)でReact Server Componentsに対応し、2026年8月15日に6.5.3が出ています。新規採用で気にすべきは機能面より、メンテナが積極的な推奨をやめている体制面です。
StyleXを採用すべきでない場面
StyleXは制約を設計の前提にしたライブラリです。次のどれかに当てはまるなら、見送った方が早く済みます。
- スタイル値を実行時に組み立てるのが中心の設計:値は静的に解析できる必要があります。動的な値はアロー関数とCSS変数で扱えますが、要素ごとにstyle属性が付きます。インラインstyleを許容できない要件では、この方式は使えません。
- 外側から他コンポーネントの内部を装飾したいとき:
stylex.whenで状態は観測できても、装飾を外から流し込む設計は成立しません。巨大なグローバルCSSを抱えたままの載せ替えは相性が悪い作りです。 - ビルド工程を持てないとき:Babel・PostCSS・unpluginのいずれかが必須で、CDNから読み込んで動かす使い方は想定されていません。Next.jsでは、WebpackでのBabelへの切り替えと、TurbopackでのBabel追加によるビルドへの影響も含めて判断します。
- 数ページの小さなサイト:数ページだけで共通コンポーネントの再利用や上書き制御も不要なら、ビルド設定を増やす利点は限られます。
版はまだ0.x系で、0.19.1のようにパッチ版でも破壊的変更が入ります。長く運用する製品では、更新のたびにCHANGELOGを読む前提で採用してください。
v0.16以降の変更点と0.19.1の破壊的変更
直近1年のマイナー版は3本ですが、公開間隔は一定ではありません。パッチ版を含めても、数か月空くことがあります。日付はnpmレジストリの公開日です(CHANGELOGの記載日とは最大9日ずれ、0.18.3はCHANGELOGが4月29日・npmが4月20日です)。
| 版 | npm公開日 | 主な変更 |
|---|---|---|
| 0.16.0 | 2025-09-26 | 子孫・兄弟セレクタ(when) |
| 0.17.0 | 2025-11-18 | unplugin、defineMarker |
| 0.17.1 | 2025-11-29 | 依存パッケージ内のStyleXをコンパイル |
| 0.18.0 | 2026-03-04 | stylex.env、作成CLI、Bun対応 |
| 0.18.1 | 2026-03-06 | sx={}プロパティ |
| 0.18.2 | 2026-03-27 | attrs復活、@layer制御 |
| 0.19.0 | 2026-06-16 | @stylexjs/atoms、ESLint 10 |
| 0.19.1 | 2026-09-15 | enableDebugClassNames削除 |
0.19.1はenableDebugClassNamesとプロパティ名付きのデバッグ用クラス名を削除し、開発と本番の識別子を揃えました。dev: trueでコンパイルしてもfontSizeのクラスは本番と同じx1j61zf2で、enableDebugClassNames: trueを残してもエラーにならず無視されます。開発時だけ出ていたプロパティ名付きのクラス名をE2Eテストのセレクタに使っていた場合は、0.19.1に上げた時点で一致しなくなるので注意してください。効いているスタイルを追うには、同じ版でFirefoxとSafariにも広がったStyleX DevTools拡張かdebugオプションを使います。debug: trueでコンパイルしたスタイルをpropsに通すと、戻り値にdata-style-srcが加わり、「パッケージ名:ファイルパス:行番号」の形で定義元が入りました(検証では"sx2267:src/App.jsx:4; sx2267:src/App.jsx:5")。
実務で効くのは0.17.1の「importしたパッケージ内のStyleXをコンパイルする」対応で、社内コンポーネントをStyleX付きのnpmパッケージとして配り、利用側のビルドでCSSをまとめて生成できます。
よくある質問
StyleXは正式版ですか?
2026年10月時点の最新版は0.19.1(2026年9月15日公開)で、1.0はまだ出ていません。Metaの主要サービスでは標準採用されているので、0.x系であることと実運用の実績は分けて評価し、パッチ版でも破壊的変更が入る前提で更新を管理してください。
npm install stylex で入りますか?
入りません。スコープなしのstylexは別作者の無関係なパッケージ(最新0.3.0)です。正しくはnpm install --save @stylexjs/stylexで、開発依存に@stylexjs/babel-pluginと、構成に応じて@stylexjs/postcss-pluginか@stylexjs/unpluginを入れます。
Tailwind CSSから乗り換えるべきですか?
Tailwind CSSで運用が回っているなら、乗り換えそのものを目的にする理由はありません。共通コンポーネントの見た目を利用側が部分的に変える設計が多く、上書きの順序と範囲を保証したいチームでは効きます。既にTailwind CSSに習熟しており、StyleXの型制約やマージ規則を必要としないなら、既存の構成を続ける判断が妥当です。
Reactがないと使えませんか?
使えます。stylex.attrsがclassと文字列のstyleを返すので、Solid・Svelte・Vue・Qwikでの利用が公式の想定用途に入っています。Viteの手順にもSvelteKitやReact Server Components向けのページがあります。
生成されたCSSはどこに出力されますか?
PostCSSプラグインの構成では、CSSファイルに1回だけ書いた@stylex;がアプリ全体分のCSSに置き換わります(Next.jsならルートレイアウトで読み込むCSS)。Viteのunpluginでは、アプリのルートで読み込んだCSSファイルにvite build時に追記されます。