@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
pnpm add -D @stylexjs/unplugin
yarn add -D @stylexjs/unplugin
bun add -D @stylexjs/unplugin
Usage by bundler
Vite
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 aimport('virtual:stylex:runtime')call from a client shim in dev.
Webpack
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
const rspack = require('@rspack/core');
const stylex = require('@stylexjs/unplugin').default;
module.exports = {
plugins: [
stylex.rspack({}),
new rspack.CssExtractRspackPlugin({ filename: 'index.css' }),
],
};Rollup
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():
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:
[serve.static]
plugins = ["@stylexjs/unplugin/bun"]Options (shared)
All options from @stylexjs/babel-plugin are forwarded.
The unplugin adds:
-
dev(boolean): defaults toNODE_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 }, defaultfalse): wrap output in@layerblocks. When set totrue, StyleX wraps each priority level in its own@layerand 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@layerordering 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 tofalseto disable it or rename it to another prop such ascss. -
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 forlightningcss. -
cssInjectionTarget((fileName: string) => boolean): pick which emitted CSS asset to append to. Defaults to preferringindex.css/style.cssor the first CSS asset; falls back to creatingstylex.cssif none exist. -
externalPackages(string[], default[]): additional packages insidenode_modulesthat should be transformed as if they were app code (useful if they ship StyleX). -
devPersistToDisk(boolean, defaultfalse, Vite): persist collected rules tonode_modules/.stylex/rules.jsonso 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 totruefor Vite/Rollup adapters.
Notes
- Each output bundle receives its own aggregated StyleX CSS. When no CSS asset
exists, the plugin emits
stylex.cssalongside 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.