Skip to content

Installation

Terminal window
npm install csszyx

The 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.

vite.config.ts
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(),
],
});

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.

CapabilityStatusWhat csszyx does with it
turbopack config keyNamed turbopack from 15.3; was experimental.turbo in 13.0.0–15.2.x, removed in 16csszyxTurbopack() writes this block
turbopack.rules loadersSupportedRuns the sz transform
File dependencies (this.addDependency)Supported — Turbopack reports a loader’s file dependencies back to its watcherWatches your @theme stylesheets so szcn groups refresh live
resolveAliasSupportedNot used — the theme registration imports a real relative path, so no app config is needed
this.emitFileNot supportedWhy the generated registration is written to .csszyx/ directly instead of emitted
this.modeNot supportedcsszyx falls back to NODE_ENV, so dev/production detection is unaffected
this.importModule / this.loadModuleNot supportedUnused
this.fsPartial — readFile onlyUnused
Deep bundle rewrite hookNot exposedWhy production mangling stays Webpack-only
  1. Add @csszyx/runtime as a direct dependency. The transform injects a bare import { _szMerge } from '@csszyx/runtime', which a strict package manager (pnpm) does not resolve as a transitive dependency.

    Terminal window
    pnpm add @csszyx/runtime
  2. Wire the loader with the csszyxTurbopack() helper. It sets the *.tsx loader rule correctly (without as, which would otherwise self-match into ./X.tsx.tsx) and merges your existing turbopack config.

    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' },
    ),
    };
  3. Keep the Tailwind safelist fresh. Point @source at the safelist file and run the csszyx watcher/prebuild around Next. Production builds fail-closed (Next Turbopack production cache is not ready) until csszyx next prebuild has 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 watch maintains the safelist while next dev runs (the example uses concurrently; a second terminal works just as well)
    • build: csszyx next prebuild must finish before next build starts — running it as part of the build script keeps plain pnpm build working

CSSzyx requires Tailwind CSS v4. Create a CSS entry point:

src/index.css
@import "tailwindcss";

Import this in your app entry point:

src/main.tsx
import './index.css';
csszyx({
development: {
debug: true, // Enable debug logging
},
production: {
mangle: true, // Obfuscate class names (z, y, x, ...) — opt-in, off by default
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.

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.

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.

  1. Install the types so the reference resolves at the top level:

    Terminal window
    pnpm add -D @csszyx/types
  2. Add a csszyx-env.d.ts at your project root (kept in your tsconfig.json include):

    /// <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/types as a direct dev dependency (pnpm add -D @csszyx/types) — unlike the React augmentation, the Solid one is not picked up transitively through csszyx, so without the direct dependency astro check / tsc report Property 'sz' does not exist on 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' }} />

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-lockfile may 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 set build.parser or the CSSZYX_PARSER env var): CSSzyx automatically falls back to oxc and 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 options
    csszyx({ build: { parser: 'oxc' } }) // or 'babel'
    Terminal window
    # one-off, e.g. in CI
    CSSZYX_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 />