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.
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 ],};Next.js Turbopack setup
Section titled “Next.js Turbopack setup”-
Add
@csszyx/runtimeas a direct dependency. The transform injects a bareimport { _szMerge } from '@csszyx/runtime', which a strict package manager (pnpm) does not resolve as a transitive dependency.Terminal window pnpm add @csszyx/runtime -
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{ safelistOutputFile: '.csszyx/next-loader-classes.html' },),}; -
Keep the Tailwind safelist fresh. Point
@sourceat the safelist file and 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, // Minify class names (z, y, x, ...) injectChecksum: true, // Inject SSR hydration checksum }, 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 three interchangeable engines. The
default is rust (a native addon) because it is the fastest; oxc and babel
are pure-JavaScript and need no binary. All three emit the exact same classes
(this is gate-tested for parity), so the engine only affects build speed, never
your output.
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 tooxcand prints the warning once. Your build succeeds and your classes are identical — nothing to fix. To silence the warning, set the parser explicitly (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 switch to a pure-JS engine:// vite.config.ts / csszyx plugin optionscsszyx({ build: { parser: 'oxc' } }) // or 'babel'Terminal window # one-off, e.g. in CICSSZYX_PARSER=oxc npm run build
Prefer oxc over babel when choosing manually — it is the faster pure-JS
engine; babel is the most compatible last resort. Effective fallback order is
rust → oxc → babel.
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 />