@stylexjs/unplugin

Universal bundler plugin for StyleX built on top of unplugin. It compiles StyleX modules, aggregates the generated CSS, and appends the result to an emitted CSS asset (or creates stylex.css as a fallback). Adapters are available for Vite/Rollup, Webpack/Rspack, esbuild, and Bun.

Install

npm install --save-dev @stylexjs/unplugin

Usage by bundler

Vite

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()],
});
  • Keep stylex.vite() before framework plugins to preserve Fast Refresh.
  • Provide a CSS entry so Vite emits an asset for the plugin to append to.
  • Dev virtual modules:
    • /virtual:stylex.css — aggregated CSS endpoint.
    • virtual:stylex:runtime — JS runtime for hot CSS reloads.
    • virtual:stylex:css-only — JS shim that only triggers CSS reloads. Add <link rel="stylesheet" href="/virtual:stylex.css" /> and either a <script type="module" src="/@id/virtual:stylex:runtime"> tag or a import('virtual:stylex:runtime') call from a client shim in dev.

Webpack

webpack.config.js
const stylex = require('@stylexjs/unplugin').default;
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  module: {
    rules: [
      // JS/TS loader here
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
    ],
  },
  plugins: [stylex.webpack({ useCSSLayers: true }), new MiniCssExtractPlugin()],
};

Use a CSS extractor so Webpack emits a stylesheet for StyleX to append to.

Rspack

rspack.config.js
const rspack = require('@rspack/core');
const stylex = require('@stylexjs/unplugin').default;

module.exports = {
  plugins: [
    stylex.rspack({}),
    new rspack.CssExtractRspackPlugin({ filename: 'index.css' }),
  ],
};

Rollup

rollup.config.mjs
import stylex from '@stylexjs/unplugin';

export default {
  plugins: [stylex.rollup({ useCSSLayers: true })],
};

esbuild

import esbuild from 'esbuild';
import stylex from '@stylexjs/unplugin';

esbuild.build({
  entryPoints: ['src/App.jsx'],
  bundle: true,
  metafile: true, // lets the plugin find CSS outputs
  plugins: [stylex.esbuild({ useCSSLayers: true })],
});

Bun

For Bun production builds, use the esbuild adapter with Bun.build():

scripts/build.mjs
import stylex from '@stylexjs/unplugin';

await Bun.build({
  entrypoints: ['src/main.jsx'],
  outdir: 'dist',
  metafile: true,
  plugins: [stylex.esbuild({ useCSSLayers: true })],
});

For Bun's dev server, add the Bun plugin entrypoint in bunfig.toml:

bunfig.toml
[serve.static]
plugins = ["@stylexjs/unplugin/bun"]

Options (shared)

All options from @stylexjs/babel-plugin are forwarded. The unplugin adds:

  • dev (boolean): defaults to NODE_ENV === 'development'. Forces dev or prod transforms.

  • importSources (string[] | {from: string, as: string}[], default ['stylex', '@stylexjs/stylex']): packages that export StyleX APIs. Also used to auto-exclude dependencies from Vite optimizeDeps/SSR.

  • useCSSLayers (boolean | { before?: string[], after?: string[], prefix?: string }, default false): wrap output in @layer blocks. When set to true, StyleX wraps each priority level in its own @layer and emits a layer-ordering header (@layer priority1, priority2, …;). This gives StyleX CSS lower specificity than any unlayered CSS in the app (per the CSS cascade: layered styles < unlayered styles). When your project also uses other CSS layers (e.g. a reset layer, a design-system layer, or a utility framework like Tailwind), pass an object to position StyleX's layers relative to yours in the @layer ordering declaration and to namespace them to avoid collisions:

    • before — extra layer names placed before StyleX's priority layers in the ordering header. Use this to ensure your reset or base layers are ordered before StyleX.
    • after — extra layer names placed after StyleX's priority layers. Useful for giving a utilities layer higher precedence than StyleX.
    • prefix — string prepended to every StyleX layer name (joined with .). Use this to namespace StyleX layers and avoid collisions with other frameworks (e.g. prefix: 'stylex'stylex.priority1).
    vite.config.ts
    stylex.vite({
      useCSSLayers: {
        before: ['reset', 'base'],
        after: ['utilities'],
        prefix: 'stylex',
      },
    });

    The generated CSS begins with a layer-ordering header followed by wrapped rules:

    @layer reset, base, stylex.priority1, stylex.priority2, utilities;
    
    @layer stylex.priority1 {
      /* low-priority rules */
    }
    @layer stylex.priority2 {
      /* higher-priority rules */
    }
  • sxPropName (string | false, default 'sx'): enables JSX shorthand such as <div sx={styles.root} /> on lowercase host elements. Set to false to disable it or rename it to another prop such as css.

  • enableLTRRTLComments (boolean): include /* @ltr */ and /* @rtl */ annotations when emitting directional CSS.

  • legacyDisableLayers (boolean): disable layer usage when emitting CSS (legacy behavior).

  • lightningcssOptions (object): passthrough options for lightningcss.

  • cssInjectionTarget ((fileName: string) => boolean): pick which emitted CSS asset to append to. Defaults to preferring index.css/style.css or the first CSS asset; falls back to creating stylex.css if none exist.

  • externalPackages (string[], default []): additional packages inside node_modules that should be transformed as if they were app code (useful if they ship StyleX).

  • devPersistToDisk (boolean, default false, Vite): persist collected rules to node_modules/.stylex/rules.json so multiple dev environments (RSC/SSR) can share CSS.

  • devMode ('full' | 'css-only' | 'off', default 'full', Vite): controls which dev middleware/virtual modules are exposed.

  • treeshakeCompensation (boolean): adds a safe side-effect import to prevent bundlers from removing StyleX themes/vars. Defaults to true for Vite/Rollup adapters.

Notes

  • Each output bundle receives its own aggregated StyleX CSS. When no CSS asset exists, the plugin emits stylex.css alongside the bundle.
  • When using CSS extraction plugins (Webpack/Rspack), ensure they run so there is a concrete stylesheet to append to.
  • For dev HMR, include the virtual stylesheet link in your HTML shell. If script tags are blocked by your framework’s asset handling, import the runtime from a tiny client shim instead of using a <script src> tag.