The runtime package provides helpers for composing className strings
dynamically. For static sz props, no helpers are needed — the compiler
produces plain strings at build time.
Runtime helpers
szr · szcn · szDecode · splitBox + class toolkit · SSR hydration validator
~0B Runtime Cost
Static sz props — zero overhead. Helpers only ship when you use them.
Tree Shakeable ESM
Import only what you use. Dead code eliminated at build time.
Resolves multiple class strings or SzObjects into a single mangle-aware className.
Falsy values (false, null, undefined) are skipped. szr is the public,
hand-written name; _sz is the identical helper the compiler injects (the _
marks generated code — do not hand-author it). Reach for szr when you build a
className from szv factory output (e.g. a code-split layout that resolves
variants at the leaf). szr concatenates; to merge with last-wins override on a
same-utility conflict use szcn (mangle-aware className merge).
import{
constszr: (...classes: SzInput[])=> string
Resolve sz object(s) and/or class strings into a single className string,
mangle-aware. This is the PUBLIC, hand-written name for the otherwise
compiler-injected _sz helper (the _ prefix marks compiler-generated code
you should not hand-author; szr is the one you call by hand).
Reach for szr when you build a className from szv factory output or sz
objects — e.g. a split/layered design system that declares variants in a
module and resolves them at the leaf:
Falsy inputs are skipped (clsx-style). szr CONCATENATES (keeps every class);
to combine with last-wins OVERRIDE on a same-utility conflict, use szcn.
szr accepts sz OBJECTS; szcn accepts className STRINGS.
@param ― classes - sz objects, class strings, or falsy values (skipped).
@returns ― The resolved className string (mangled in a production build).
Creates a variant-based sz object factory with strong TypeScript inference.
TypeScript catches invalid variant values at compile time — no runtime
surprises. All variant objects are plain sz objects, fully compatible
with the sz prop and
@csszyx ― /dynamic's sz() function.
@param ― config - Variant configuration with base, variants, and defaultVariants
@returns ― A factory function that accepts a variant selection and returns an SzObject
Creates a variant-based sz object factory with strong TypeScript inference.
TypeScript catches invalid variant values at compile time — no runtime
surprises. All variant objects are plain sz objects, fully compatible
with the sz prop and
@csszyx ― /dynamic's sz() function.
@param ― config - Variant configuration with base, variants, and defaultVariants
@returns ― A factory function that accepts a variant selection and returns an SzObject
Resolve sz object(s) and/or class strings into a single className string,
mangle-aware. This is the PUBLIC, hand-written name for the otherwise
compiler-injected _sz helper (the _ prefix marks compiler-generated code
you should not hand-author; szr is the one you call by hand).
Reach for szr when you build a className from szv factory output or sz
objects — e.g. a split/layered design system that declares variants in a
module and resolves them at the leaf:
Falsy inputs are skipped (clsx-style). szr CONCATENATES (keeps every class);
to combine with last-wins OVERRIDE on a same-utility conflict, use szcn.
szr accepts sz OBJECTS; szcn accepts className STRINGS.
@param ― classes - sz objects, class strings, or falsy values (skipped).
@returns ― The resolved className string (mangled in a production build).
Resolve sz object(s) and/or class strings into a single className string,
mangle-aware. This is the PUBLIC, hand-written name for the otherwise
compiler-injected _sz helper (the _ prefix marks compiler-generated code
you should not hand-author; szr is the one you call by hand).
Reach for szr when you build a className from szv factory output or sz
objects — e.g. a split/layered design system that declares variants in a
module and resolves them at the leaf:
Falsy inputs are skipped (clsx-style). szr CONCATENATES (keeps every class);
to combine with last-wins OVERRIDE on a same-utility conflict, use szcn.
szr accepts sz OBJECTS; szcn accepts className STRINGS.
@param ― classes - sz objects, class strings, or falsy values (skipped).
@returns ― The resolved className string (mangled in a production build).
szr('p-4','bg-blue-500','text-white')} />
// Falsy values are skipped automatically
const
constisActive: true
isActive=true;
const
consthasError: false
hasError=false;
<
any
div
className: string
className={
functionszr(...classes: SzInput[]): string
Resolve sz object(s) and/or class strings into a single className string,
mangle-aware. This is the PUBLIC, hand-written name for the otherwise
compiler-injected _sz helper (the _ prefix marks compiler-generated code
you should not hand-author; szr is the one you call by hand).
Reach for szr when you build a className from szv factory output or sz
objects — e.g. a split/layered design system that declares variants in a
module and resolves them at the leaf:
Falsy inputs are skipped (clsx-style). szr CONCATENATES (keeps every class);
to combine with last-wins OVERRIDE on a same-utility conflict, use szcn.
szr accepts sz OBJECTS; szcn accepts className STRINGS.
@param ― classes - sz objects, class strings, or falsy values (skipped).
@returns ― The resolved className string (mangled in a production build).
Merges className strings with last-wins override per utility — the merge to
use at the single resolution point of a layered component (typically the leaf
Box), and for merging a part’s own defaults with a consumer override. Falsy
inputs (false, null, undefined, '') are skipped.
npm tailwind-merge cannot do this job here: in a production build csszyx
mangles owned classes (gap-2 → q3, gap-8 → q7), and tailwind-merge
can’t tell q3/q7 are the same utility. szcn decodes each token through the
runtime reverse mangle map (window.__csszyx.decode) before grouping, so
overrides keep working with mangling on.
functionszcn(...inputs: ClassInput[]): string
Merge className strings with last-wins override per utility, mangle-aware and
memoized (see the memo note above — repeated inputs return in one Map lookup).
Intended for the single resolution point in a layered design-system component
(typically at the leaf Box): combine the component's default classes with the
forwarded override so the override wins on a same-utility collision, while
keeping production mangling intact (unlike npm tailwind-merge).
@param ― inputs - Class strings; falsy inputs (false/null/undefined/'') are skipped.
Merge className strings with last-wins override per utility, mangle-aware and
memoized (see the memo note above — repeated inputs return in one Map lookup).
Intended for the single resolution point in a layered design-system component
(typically at the leaf Box): combine the component's default classes with the
forwarded override so the override wins on a same-utility collision, while
keeping production mangling intact (unlike npm tailwind-merge).
@param ― inputs - Class strings; falsy inputs (false/null/undefined/'') are skipped.
szcn('md:gap-2','gap-8');// → 'md:gap-2 gap-8' variants isolate — both kept
Fail-safe contract: a token whose conflict group can’t be determined
confidently is NEVER merged away — it keys by itself, so the worst case is two
classes co-existing (the pre-merge status quo), never a wrongly-dropped class.
A shorthand appearing later removes the longhands it subsumes; a longhand
appearing later only refines. Covers padding, margin, inset, and border-radius:
szcn('pb-4','p-8');// → 'p-8' p covers pb
szcn('p-4','pb-8');// → 'p-4 pb-8' pb only refines the bottom
Logical sides (ps/pe, ms/me) are subsumed by their p/m shorthands.
For inset and rounded the coverage is physical-only: inset/rounded do
not subsume logical start/end / rounded-s* tokens, which could flip under
RTL — those stay keep-both (still cascade-correct).
Eight prefixes span more than one CSS property (text-sm is font-size,
text-red-500 is color). For these, szcn classifies the token value into
a property group: same group → last wins, different group → co-exist,
unclassifiable → keep-both.
// border / divide / ring / outline: width / color / style
szcn('border-2','border-4');// → 'border-4'
// flex: shorthand / direction / wrap
szcn('flex-row','flex-col');// → 'flex-col'
szcn('flex','flex-1');// → 'flex flex-1' bare flex is display
Still keep-both by design: directional/axis forms of border-family prefixes
(border-t-2 vs border-2, divide-x-2 vs divide-y-2), CSS-variable values
(text-(--brand) — the type is unknown), and any value csszyx cannot classify.
On a production-mangled build, szcn also encodes its output. A class name
your component resolves at runtime as a plain string — a prop mapped to
'flex-col', a template like `gap-${n}` — never went through the
compiler, so without encoding it would reach the DOM in its original spelling
while the shipped CSS only contains the mangled selector. szcn closes that
gap: every surviving token is looked up in the runtime mangle map and leaves
the merge in mangled form.
The lookup is single-pass and idempotent. Already-mangled tokens, authored
literals reserved via production.mangleExclude, and external (non-csszyx)
class names pass through unchanged — token allocation guarantees a mangled
token can never spell a censused class name, so one map lookup is unambiguous.
In dev, or on a build without mangling, the encode step is an identity.
Code that inspects a className for a utility by its original spelling must
decode first — see szDecode.
Registers custom token names so szcn can classify classes built from them.
Idempotent and additive; the build plugin calls it automatically from the
app’s @theme blocks — call it manually only for hand-written CSS utilities.
Guard rails — both fall back to keep-both with a one-time dev warning, never a
wrong merge:
A name that shadows a built-in value keyword of an affected prefix is
rejected: a color named cover would make szcn misread bg-cover
(background-size) as a color and merge it wrongly.
A name registered in two conflicting categories (both a color and a text
size — text-huge becomes unclassifiable) is dropped from both, and the drop
is remembered: later registrations of either side are rejected too, so
registration order can never resurrect an ambiguous name into one category.
Maps a mangled class token back to its original name. On any build shape where
the token is not mangled — dev, mangle: false, an authored literal, an
external class — it returns the input unchanged, so it is always safe to call.
functionszDecode(token: string): string
Reach for it whenever code inspects a className for a utility by its
original spelling. String checks like startsWith('w-') silently stop
matching on a mangled build (the DOM carries q3, not w-full); decoding
first keeps the check correct on every build:
Variant prefixes decode with the token (szDecode('x7') can return
'md:hover:w-full'), so strip the prefix before comparing the bare utility if
your check targets the base class.
Not a runtime function: the build rewrites every
szs={{…}}
call site to szsc={{ slot: "class string" }}, so the component receives plain
strings on a dedicated prop and forwards them into a child className with no
helper. Declare both faces from one slot union with SzsProps:
CSR recovery is now opted in per-element via the szRecover JSX
attribute ("csr" or "dev-only"); no global flag needed. See
SSR & Hydration → Recovery Tokens.
All variant class combinations are catalogued at build time — the compiler extracts every
possible output and adds them to the Tailwind safelist so CSS is pre-generated.
When a caller passes one flat className to a component that renders nested
elements, the styles often belong on different elements — the margin on the
outer frame, the padding on the inner content. splitBox partitions a className
at the CSS box-model border line into { outer, inner }. Every token lands in
exactly one bucket (no loss, no duplication) and keeps its variant prefix.
functionsplitBox(className: string, options?: {
outer?: BoxSelector[];// force these onto the outer node
inner?: BoxSelector[];// force these onto the inner node
The split follows the box model: outer = border-outward (margin, position,
border, sizing, background, shadow, transform, visibility); inner =
border-inward (padding, overflow, display, layout, gap, text, paint-inside,
interactivity). The class-token → box-role map is generated from the
compiler’s property tables, so it never drifts. Every default is overridable.
The category-aware toolkit exposes csszyx’s class knowledge as primitives.
csszyx owns the truth (a class’s box-role + category); your project owns the
rule (which dependent classes to add, under which conditions) — no rule-DSL,
no hardcoded Tailwind vocabulary.
The sz-object twin of splitBox: partitions an sz object (not a className)
into { outer, inner } sz objects, so a component built with szv stays
sz-native and keeps the compiler’s auto-safelisting. Each key routes to the same
side its emitted class would — splitBoxSz(x) ≡ splitBox(compile(x)).
Arrays are deep-merged (last-write-wins); null / false / undefined and
empty objects yield { outer: {}, inner: {} }. A raw className string has no
sz-object form, so it throws in development.
Removes sz before a component spreads ...rest onto a host element. Compiled
components never carry a leftover sz, but a file that was not compiled
(e.g. a workspace package missing from compileSources) keeps its raw sz,
which leaks to the DOM as sz="[object Object]". stripSzProps drops it and,
in development, warns once when the leaked sz is a raw object.
When production mangling is on, csszyx embeds a mangle map plus a checksum so the
runtime can detect a map that was altered or drifted out of sync.
verifyMangleChecksumAsyncrecomputes the map’s checksum with the Web Crypto
API (crypto.subtle) and compares it to the expected value — no WASM core
required, which makes it usable in edge/serverless runtimes.
functionverifyMangleChecksumAsync(
expectedChecksum: string,
map?: MangleMap, // loaded + schema-validated from the DOM when omitted
):Promise<boolean>// true only when the map is present and its checksum matches