Installation
Install
Section titled “Install”npm install csszyxpnpm add csszyxyarn add csszyxThe csszyx umbrella package includes the compiler, runtime, types, and
build plugin. The command-line tools (migrate, next, type generation)
ship separately as @csszyx/cli — run them with npx @csszyx/cli <command>
or install with pnpm add -D @csszyx/cli. A Next.js project also adds
@csszyx/unplugin directly, because its configs name that package (the
Next.js steps below say where); csszyx init does this for you.
For portable sz autocomplete through compatible TypeScript editor hosts,
install the preview @csszyx/ts-plugin separately and follow the
TypeScript Autocomplete Plugin guide. It is an
authoring tool, not a build dependency.
Setup by Platform
Section titled “Setup by Platform”import { defineConfig } from 'vite';import csszyx from 'csszyx/vite';import tailwindcss from '@tailwindcss/vite';import react from '@vitejs/plugin-react';
export default defineConfig({ plugins: [ // Order matters: csszyx → tailwindcss → react ...csszyx(), tailwindcss(), react(), ],});import type { NextConfig } from 'next';
const nextConfig: NextConfig = { reactStrictMode: true, webpack: (config) => { const csszyxWebpack = require('@csszyx/unplugin/webpack').default; config.plugins.push(csszyxWebpack()); return config; },};
export default nextConfig;const csszyxWebpack = require('@csszyx/unplugin/webpack').default;
module.exports = { plugins: [ csszyxWebpack(), // ... other plugins ],};What the Turbopack lane can and cannot use
Section titled “What the Turbopack lane can and cannot use”csszyx’s Turbopack support is a webpack-compatible loader wired through
turbopack.rules, so it is bounded by how much of the webpack loader API
Turbopack implements. This is the state csszyx builds on, taken from the
Next.js 16 documentation and validated against Next.js 16.2.x, the version
the csszyx playground pins and CI runs.
| Capability | Status | What csszyx does with it |
|---|---|---|
turbopack config key | Named turbopack from 15.3; was experimental.turbo in 13.0.0–15.2.x, removed in 16 | csszyxTurbopack() writes this block |
turbopack.rules loaders | Supported | Runs the sz transform |
File dependencies (this.addDependency) | Supported — Turbopack reports a loader’s file dependencies back to its watcher | Watches your @theme stylesheets so szcn groups refresh live |
resolveAlias | Supported | Not used — the theme registration imports a real relative path, so no app config is needed |
this.emitFile | Not supported | Why the generated registration is written to .csszyx/ directly instead of emitted |
this.mode | Not supported | csszyx falls back to NODE_ENV, so dev/production detection is unaffected |
this.importModule / this.loadModule | Not supported | Unused |
this.fs | Partial — readFile only | Unused |
| Deep bundle rewrite hook | Not exposed | Why production mangling stays Webpack-only |
Next.js Turbopack setup
Section titled “Next.js Turbopack setup”-
Add
@csszyx/runtimeand@csszyx/unpluginas direct dependencies. The transform injects a bareimport { _szMerge } from '@csszyx/runtime', and the two config files below name@csszyx/unplugin/nextand@csszyx/unplugin/postcssby package; a strict package manager (pnpm, Yarn PnP) does not resolve either as a transitive dependency ofcsszyx.csszyx initinstalls both.Terminal window pnpm add @csszyx/runtime @csszyx/unplugin -
Wire the loader with the
csszyxTurbopack()helper. It sets the*.tsxloader rule correctly (withoutas, which would otherwise self-match into./X.tsx.tsx) and merges your existingturbopackconfig.next.config.mjs import { csszyxTurbopack } from '@csszyx/unplugin/next';export default {turbopack: csszyxTurbopack({}, // your existing turbopack config (resolveAlias, other rules) is merged),}; -
Let PostCSS point Tailwind at the safelist. Turbopack gives csszyx no hook into your stylesheet, so the
@sourcedirective the other bundlers inject for you comes from a PostCSS plugin here. List it before@tailwindcss/postcss— Tailwind compiles the stylesheet in its own pass, so a plugin after it is too late.csszyx initwrites this file for you.postcss.config.mjs export default {plugins: {'@csszyx/unplugin/postcss': {},'@tailwindcss/postcss': {},},};The plugin names the safelist csszyx writes by default,
.csszyx/csszyx-classes.txt, relative to each stylesheet that importstailwindcss, and skips a path you already wrote yourself. If you pointcsszyx next prebuild,csszyx next watchor the loader’ssafelistOutputFilesomewhere else, list every file it should name:'@csszyx/unplugin/postcss': { safelistFiles: ['.csszyx/my-classes.txt'] }. A safelist that does not exist yet is fine: Tailwind scans nothing for it and does not fail. -
Keep the Tailwind safelist fresh. Run the csszyx watcher/prebuild around Next. Production builds fail-closed (
Next Turbopack production cache is not ready) untilcsszyx next prebuildhas seeded the safelist and generation manifest, so wire both flows into your scripts:package.json {"scripts": {"dev": "concurrently \"csszyx next watch 'app/**/*.tsx'\" \"next dev --turbopack\"","build": "csszyx next prebuild 'app/**/*.tsx' && next build --turbopack"}}- dev:
csszyx next watchmaintains the safelist whilenext devruns (the example uses concurrently; a second terminal works just as well) - build:
csszyx next prebuildmust finish beforenext buildstarts — running it as part of thebuildscript keeps plainpnpm buildworking
- dev:
Tailwind CSS v4 Entry Point
Section titled “Tailwind CSS v4 Entry Point”CSSzyx requires Tailwind CSS v4. Create a CSS entry point:
@import "tailwindcss";Import this in your app entry point:
import './index.css';Plugin Options
Section titled “Plugin Options”csszyx({ development: { debug: true, // Enable debug logging }, production: { mangle: true, // Obfuscate class names (z, y, x, ...) — opt-in, off by default }, build: { astBudgetLimit: 50_000, // Per-file AST node cap; file skipped (warned) past it scanCss: 'src/index.css', // CSS file(s) to scan for @theme tokens },});For per-element hydration recovery (szRecover="csr" / szRecover="dev-only")
see SSR & Hydration → Recovery Tokens.
Optional: Initialize Runtime
Section titled “Optional: Initialize Runtime”For SSR hydration safety, initialize the runtime in your app entry:
import { initRuntime } from '@csszyx/runtime';
initRuntime({ development: process.env.NODE_ENV === 'development', strictHydration: true,});This is optional — CSSzyx works without it, but SSR hydration guards require
it to be called before first render. Per-element CSR recovery is opted in
via the szRecover JSX attribute on individual elements; see
SSR & Hydration.
TypeScript
Section titled “TypeScript”The sz prop comes from a JSX type augmentation in @csszyx/types. It is
picked up automatically when you import from csszyx in a hoisting package
manager. With a strict package manager (pnpm), or if your app uses sz
without importing csszyx directly, add a one-line reference so TypeScript
loads the augmentation.
-
Install the types so the reference resolves at the top level:
Terminal window pnpm add -D @csszyx/types -
Add a
csszyx-env.d.tsat your project root (kept in yourtsconfig.jsoninclude):/// <reference types="@csszyx/types/jsx" />SolidJS keeps its own JSX namespace, so reference the Solid augmentation instead:
/// <reference types="@csszyx/types/jsx-solid" />Solid consumers must add
@csszyx/typesas a direct dev dependency (pnpm add -D @csszyx/types) — unlike the React augmentation, the Solid one is not picked up transitively throughcsszyx, so without the direct dependencyastro check/tscreportProperty 'sz' does not existon Solid elements.
Then the sz prop is typed on every element:
// ✅ TypeScript knows this is valid<div sz={{ p: 4, bg: 'blue-500', hover: { bg: 'blue-700' } }} />
// ❌ TypeScript error: 'red-999' is not a valid color<div sz={{ bg: 'red-999' }} />Troubleshooting
Section titled “Troubleshooting”Classes not applying — make sure your CSS entry point uses the full Tailwind v4 bundle:
/* ✅ correct — includes theme + preflight + utilities */@import "tailwindcss";
/* ❌ wrong — utilities only, theme variables undefined */@import "tailwindcss/utilities";Partial imports like tailwindcss/utilities only generate static-value utilities
(.border-0, .m-px). Scale-dependent utilities like p-4, rounded-sm, text-xs
require the theme layer (--spacing, --radius-sm, --text-xs) and will silently
produce no CSS without it.
Monorepo with both Tailwind v3 and v4 — if your workspace has other packages
that depend on Tailwind v3, your package’s CSS resolver may accidentally pick up v3
instead of v4, causing @import "tailwindcss" to fail or generate no theme utilities.
Fix: explicitly declare tailwindcss as a dependency in each package that uses CSSzyx:
{ "dependencies": { "csszyx": "^0.4.0", "tailwindcss": "^4.0.0" }}Plugin order warnings — CSSzyx must be before Tailwind and React in the plugins array.
“No prebuilt native binary” warning, or a native engine unavailable build
error — CSSzyx parses your source with one engine that ships as two artifacts.
The default is rust (a native addon) because it is the fastest. The same
engine is also compiled to WebAssembly and ships inside @csszyx/core itself,
so a machine with no native binary still runs the engine every other machine
runs — identical output, only parse speed differs.
Because both artifacts are the same engine, neither can change your classes. There is nothing else to fall back to and nothing else to choose.
The native engine ships as per-platform optional dependencies
(@csszyx/core-<platform>) — one is auto-selected for your OS/CPU/libc during a
normal npm install. You do not run any extra command or compile anything;
it is just a prebuilt download. It can be absent in three situations:
- Unsupported platform — your OS/arch/libc is outside the prebuilt set (the targets are macOS arm64/x64, Linux gnu+musl arm64/x64, Windows arm64/x64). Anything else (FreeBSD, 32-bit ARM, riscv64, Alpine on an odd arch, …) has no matching package.
- Optional dependencies were skipped — installing with
--omit=optional/--no-optional, or in a minimal/sandboxed CI or Docker image that strips optional deps, removes the binary even on a supported platform. - A cross-platform lockfile — a lockfile generated on one OS (say macOS) then
installed on another (say Linux CI) with
--frozen-lockfilemay not have the other platform’s optional entry resolved, so the binary is missing.
What happens, and what to do:
-
You only inherited the default
rust(you did not setbuild.parseror theCSSZYX_PARSERenv var): CSSzyx automatically falls back to the engine’s wasm build and prints the warning once. Your build succeeds with identical classes; only parse speed differs. To silence it, pin the wasm build (next bullet) or install the matching@csszyx/core-<platform>package / stop omitting optional deps. -
You explicitly chose
rust(via config or env): this stays a hard error on purpose — an explicit choice is never silently swapped. Either install the native binary for your platform, or ask for the wasm build by name:// vite.config.ts / csszyx plugin optionscsszyx({ build: { parser: 'wasm' } })Terminal window # one-off, e.g. in CICSSZYX_PARSER=wasm npm run build
'rust' and 'wasm' are the only values. They are the same engine, so the
choice is about how it loads, never about what it emits.
TypeScript errors with sz prop (Property 'sz' does not exist on type 'DetailedHTMLProps<...>') — the JSX augmentation is not loaded. Add
@csszyx/types and a csszyx-env.d.ts with
/// <reference types="@csszyx/types/jsx" />; see TypeScript
above. This is common under pnpm and when csszyx is never imported directly.
Hydration warnings in Next.js — initialize the runtime in your root layout
and opt in to per-element recovery with <section szRecover="csr">…</section>
where mismatches are expected. See SSR & Hydration.
sz props not working in Astro MDX — the MDX Vite plugin compiles JSX to
runtime calls before csszyx runs, so sz={{ ... }} props written directly in
.mdx files are never transformed and render as sz="[object Object]".
Fix: put sz props in .tsx components and import them in MDX:
// src/components/MyDemo.tsx ← compiled by csszyx ✅export function MyDemo() { return <div sz={{ p: 4, bg: 'blue-500' }}>Hello</div>;}{/* src/pages/guide.mdx */}import { MyDemo } from '../../components/MyDemo.tsx';
<MyDemo />