Skip to content

Runtime Helpers

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.

import { szr, szcn } from '@csszyx/runtime';

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 {
const szr: (...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:

import { szr, szv } from '@csszyx/runtime';
const cardSz = szv({ variants: { pad: { lg: { p: 8 } } } });
const cls = szr(cardSz({ pad: 'lg' }), isWide && stackSz({ gap: 'xl' }));

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.

@paramclasses - sz objects, class strings, or falsy values (skipped).

@returnsThe resolved className string (mangled in a production build).

szr
,
function szv<V extends VariantSchema>(config: SzvConfig<V>): (selection?: VariantSelection<V>) => 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.

@paramconfig - Variant configuration with base, variants, and defaultVariants

@returnsA factory function that accepts a variant selection and returns an SzObject

@example

import { szv } from 'csszyx';
const buttonSz = szv({
base: { display: 'inline-flex', items: 'center', rounded: 'md', weight: 'medium' },
variants: {
variant: {
default: { bg: 'primary', text: 'primary-foreground' },
outline: { border: true, borderColor: 'blue-500', bg: 'transparent' },
ghost: { hover: { bg: 'accent' } },
},
size: {
sm: { h: 9, px: 3, text: 'sm' },
md: { h: 10, px: 4 },
lg: { h: 11, px: 8 },
},
},
defaultVariants: { variant: 'default', size: 'md' },
});
// Usage — consistent with sz prop, TypeScript catches invalid values
<button sz={buttonSz({ variant: 'outline', size: 'sm' })} />
// Compose with sz array syntax
<button sz={[
buttonSz({ variant: props.variant, size: props.size }),
isLoading && { opacity: 50, cursor: 'wait' },
]} />
// With

@csszyx

/dynamic for fully runtime-resolved styling const { sz } = useSz(); <button className={sz(buttonSz({ variant: props.variant }))} />

szv
} from '@csszyx/runtime';
const
const cardSz: (selection?: VariantSelection<{
pad: {
lg: {
p: number;
};
};
}> | undefined) => SzObject
cardSz
=
szv<{
pad: {
lg: {
p: number;
};
};
}>(config: SzvConfig<{
pad: {
lg: {
p: number;
};
};
}>): (selection?: VariantSelection<{
pad: {
lg: {
p: number;
};
};
}> | undefined) => 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.

@paramconfig - Variant configuration with base, variants, and defaultVariants

@returnsA factory function that accepts a variant selection and returns an SzObject

@example

import { szv } from 'csszyx';
const buttonSz = szv({
base: { display: 'inline-flex', items: 'center', rounded: 'md', weight: 'medium' },
variants: {
variant: {
default: { bg: 'primary', text: 'primary-foreground' },
outline: { border: true, borderColor: 'blue-500', bg: 'transparent' },
ghost: { hover: { bg: 'accent' } },
},
size: {
sm: { h: 9, px: 3, text: 'sm' },
md: { h: 10, px: 4 },
lg: { h: 11, px: 8 },
},
},
defaultVariants: { variant: 'default', size: 'md' },
});
// Usage — consistent with sz prop, TypeScript catches invalid values
<button sz={buttonSz({ variant: 'outline', size: 'sm' })} />
// Compose with sz array syntax
<button sz={[
buttonSz({ variant: props.variant, size: props.size }),
isLoading && { opacity: 50, cursor: 'wait' },
]} />
// With

@csszyx

/dynamic for fully runtime-resolved styling const { sz } = useSz(); <button className={sz(buttonSz({ variant: props.variant }))} />

szv
({
SzvConfig<{ pad: { lg: { p: number; }; }; }>.variants?: {
pad: {
lg: {
p: number;
};
};
} | undefined
variants
: {
pad: {
lg: {
p: number;
};
}
pad
: {
lg: {
p: number;
}
lg
: {
p: number
p
: 8 } } } });
const
const cls: string
cls
=
function szr(...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:

import { szr, szv } from '@csszyx/runtime';
const cardSz = szv({ variants: { pad: { lg: { p: 8 } } } });
const cls = szr(cardSz({ pad: 'lg' }), isWide && stackSz({ gap: 'xl' }));

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.

@paramclasses - sz objects, class strings, or falsy values (skipped).

@returnsThe resolved className string (mangled in a production build).

szr
(
const cardSz: (selection?: VariantSelection<{
pad: {
lg: {
p: number;
};
};
}> | undefined) => SzObject
cardSz
({
pad?: "lg" | null | undefined
pad
: 'lg' }), 'bg-blue-500', false, null, 'text-white');

Usage:

// Basic concatenation
<
any
div
className: string
className
={
function szr(...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:

import { szr, szv } from '@csszyx/runtime';
const cardSz = szv({ variants: { pad: { lg: { p: 8 } } } });
const cls = szr(cardSz({ pad: 'lg' }), isWide && stackSz({ gap: 'xl' }));

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.

@paramclasses - sz objects, class strings, or falsy values (skipped).

@returnsThe resolved className string (mangled in a production build).

szr
('p-4', 'bg-blue-500', 'text-white')} />
// Falsy values are skipped automatically
const
const isActive: true
isActive
= true;
const
const hasError: false
hasError
= false;
<
any
div
className: string
className
={
function szr(...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:

import { szr, szv } from '@csszyx/runtime';
const cardSz = szv({ variants: { pad: { lg: { p: 8 } } } });
const cls = szr(cardSz({ pad: 'lg' }), isWide && stackSz({ gap: 'xl' }));

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.

@paramclasses - sz objects, class strings, or falsy values (skipped).

@returnsThe resolved className string (mangled in a production build).

szr
('base',
const isActive: true
isActive
&& 'active',
const hasError: false
hasError
&& 'error')} />

szcn(...classes) — Mangle-Aware Override Merge

Section titled “szcn(...classes) — Mangle-Aware Override Merge”

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.

function szcn(...inputs: (string | false | null | undefined)[]): string

npm tailwind-merge cannot do this job here: in a production build csszyx mangles owned classes (gap-2q3, gap-8q7), 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.

function szcn(...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).

@paraminputs - Class strings; falsy inputs (false/null/undefined/'') are skipped.

@returnsThe merged className string.

@example szcn('gap-2 p-4', 'gap-8') // → 'p-4 gap-8' (gap-8 overrides gap-2)

szcn
('gap-2 p-4', 'gap-8'); // → 'p-4 gap-8' gap-8 overrides gap-2
function szcn(...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).

@paraminputs - Class strings; falsy inputs (false/null/undefined/'') are skipped.

@returnsThe merged className string.

@example szcn('gap-2 p-4', 'gap-8') // → 'p-4 gap-8' (gap-8 overrides gap-2)

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
szcn('ml-2', 'mx-4'); // → 'mx-4'
szcn('top-2', 'inset-4'); // → 'inset-4'
szcn('rounded-tl-sm', 'rounded-lg'); // → 'rounded-lg'

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.

// text: size / color / align / wrap / overflow
szcn('text-base', 'text-sm'); // → 'text-sm'
szcn('text-red-500', 'text-sm'); // → 'text-red-500 text-sm'
szcn('text-sm/6', 'text-lg/7'); // → 'text-lg/7' modifiers stay in-group
// font: family vs weight
szcn('font-semibold', 'font-normal'); // → 'font-normal'
szcn('font-sans', 'font-bold'); // → 'font-sans font-bold'
// bg: color / size / position / repeat / attachment / clip / image
szcn('bg-red-500', 'bg-blue-500'); // → 'bg-blue-500'
szcn('bg-red-500', 'bg-cover'); // → 'bg-red-500 bg-cover'
// 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.

Production encode of runtime-resolved classes

Section titled “Production encode of runtime-resolved classes”

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.

interface SzcnThemeGroups {
colors?: readonly string[]; // 'brand' → text-brand, bg-brand, border-brand, …
textSizes?: readonly string[]; // 'huge' → text-huge
fontFamilies?: readonly string[]; // 'display' → font-display
fontWeights?: readonly string[]; // 'chunky' → font-chunky
}
function registerSzcnGroups(groups: SzcnThemeGroups): void
import { registerSzcnGroups } from '@csszyx/runtime';
// once, at app startup — not per-render
registerSzcnGroups({ colors: ['brand', 'tag-blue-bg'], fontFamilies: ['display'] });

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.

function szDecode(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:

import { szDecode } from '@csszyx/runtime';
const hasWidthClass = (className: string) =>
className.split(/\s+/).some(t => szDecode(t).startsWith('w-'));

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:

import { szcn } from '@csszyx/runtime';
import type { SzsProps } from '@csszyx/types';
function Card({ szsc }: SzsProps<'title' | 'body'>) {
// Slot default + consumer override — szcn resolves conflicts last-wins.
return <h3 className={szcn('font-medium text-base', szsc?.title)} />;
}

Merges multiple SzObjects. Later objects override earlier ones for the same key.

function _szMerge(...objects: SzObject[]): string

Usage:

const baseStyles = { p: 4, bg: 'gray-100', rounded: 'md' };
const activeStyles = { bg: 'blue-500', color: 'white' };
<div className={_szMerge(baseStyles, isActive ? activeStyles : {})} />

Converts a color value to the correct CSS variable format for use in style props or CSS custom properties.

import { __szColorVar } from 'csszyx/lite';
function __szColorVar(value: string): string
InputOutput
'blue-500'var(--color-blue-500)
'#ff0000''#ff0000' (passthrough)
'--my-var'var(--my-var)
'white'var(--color-white)

Usage:

// Dynamic color for style prop (not sz prop)
<div style={{
'--my-accent': __szColorVar(accentColor),
}} />
// Combined with CSS custom property
<svg style={{ fill: __szColorVar('blue-500') }} />

Initializes the CSSzyx runtime. Call once at app startup.

interface RuntimeConfig {
development?: boolean; // Enable dev mode features
strictHydration?: boolean; // Treat hydration warnings as errors
debug?: boolean; // Enable console debug logging
}
function initRuntime(config?: Partial<RuntimeConfig>): void

Usage:

// In your app entry point
import { initRuntime } from '@csszyx/runtime';
initRuntime({
development: process.env.NODE_ENV === 'development',
strictHydration: true,
debug: process.env.NODE_ENV === 'development',
});

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.

Returns the current runtime configuration (read-only copy).

function getRuntimeConfig(): Required<RuntimeConfig>

Returns true if initRuntime() has been called.

function isRuntimeInitialized(): boolean

Creates a type-safe variant factory that returns sz objects. CVA-equivalent for csszyx.

function szv<V extends VariantSchema>(
config: SzvConfig<V>
): (selection?: VariantSelection<V>) => SzObject
import { szv } from '@csszyx/runtime';
// or:
import { szv } from 'csszyx';
const buttonSz = szv({
base: { display: 'inline-flex', items: 'center', rounded: 'md' },
variants: {
variant: {
default: { bg: 'primary', text: 'primary-foreground' },
outline: { border: true, borderColor: 'blue-500' },
},
size: {
sm: { h: 9, px: 3 },
md: { h: 10, px: 4 },
},
},
defaultVariants: { variant: 'default', size: 'md' },
});
<button sz={buttonSz({ variant: 'outline', size: 'sm' })} />

TypeScript infers valid keys/values from the config literal — no manual annotations needed. See the szv() guide for full docs.

Numeric variant keys are fully supported — useful for index-based variants:

const itemSz = szv({
base: { rounded: 'sm', shrink: 0 },
variants: {
idx: {
0: { opacity: 50 },
1: { opacity: 70 },
2: { opacity: 90 },
},
color: {
normal: { bg: '#2dd597' },
reverse: { bg: '#a78bfa' },
},
},
defaultVariants: { idx: 0, color: 'normal' },
});
<div sz={itemSz({ idx: 1, color: 'reverse' })} />
// → "rounded-sm shrink-0 opacity-70 bg-[#a78bfa]"

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.

Pass an array to the sz prop to compose multiple sz objects with conditional items:

// Static items → extracted at build time (zero runtime)
// Conditional items → _szMerge at runtime
<div sz={[
{ display: 'flex', items: 'center', p: 4 },
isActive && { bg: 'blue-500' },
isDisabled && { opacity: 50, cursor: 'not-allowed' },
]} />
// Compose szv() output with extra overrides
<button sz={[
buttonSz({ variant: props.variant }),
isLoading && { opacity: 50, cursor: 'wait' },
]} />

The compiler pre-computes all static objects at build time; only the conditional merge paths use _szMerge at runtime.

splitBox(className, options?) — Nested Element Routing

Section titled “splitBox(className, options?) — Nested Element Routing”

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.

function splitBox(className: string, options?: {
outer?: BoxSelector[]; // force these onto the outer node
inner?: BoxSelector[]; // force these onto the inner node
fallback?: 'outer' | 'inner'; // unrecognized token → default 'outer'
}): { outer: string; inner: string }
// box-role | category | class-prefix | category+value pair
type BoxSelector = string | Readonly<Record<string, string>>

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.

import { splitBox } from '@csszyx/runtime';
const { outer, inner } = splitBox('m-4 px-2 md:flex');
// outer: "m-4" inner: "px-2 md:flex"
<Frame className={outer}>
<Content className={inner} />
</Frame>

classify / has / pick / omit — Class Toolkit

Section titled “classify / has / pick / omit — Class Toolkit”

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.

function classify(token: string): { role: 'outer' | 'inner'; category: string } | undefined
function has(classes: string, selector: BoxSelector): boolean
function pick(classes: string, selector: BoxSelector): string
function omit(classes: string, selector: BoxSelector): string
import { splitBox, has, _szMerge } from '@csszyx/runtime';
// A scroller should scroll only when the outer frame clips:
const { outer, inner } = splitBox(className);
const dep = has(outer, { overflow: 'hidden' }) ? 'overflow-y-auto h-full' : '';
<Frame className={outer}>
<Scroll className={_szMerge(inner, dep)} />
</Frame>

splitBoxSz(sz, options?) — sz-Object Routing

Section titled “splitBoxSz(sz, options?) — sz-Object Routing”

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

function splitBoxSz(sz: SzInput, options?: {
outer?: BoxSelector[];
inner?: BoxSelector[];
fallback?: 'outer' | 'inner'; // unrecognized key → default 'outer'
}): { outer: SzObject; inner: SzObject }
import { splitBoxSz } from '@csszyx/runtime';
splitBoxSz({ m: 4, px: 2 });
// → { outer: { m: 4 }, inner: { px: 2 } }
// Variants route by their inner property; arrays flatten; inner wins ties.
splitBoxSz({ gap: 2, hover: { px: 1 }, md: { m: 4 } });
// → { outer: { md: { m: 4 } }, inner: { gap: 2, hover: { px: 1 } } }
// Force flex-item utilities onto the frame:
splitBoxSz({ grow: 2, self: 'center' }, { outer: ['grow', 'self'] });
// → { outer: { grow: 2, self: 'center' }, inner: {} }

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.

The sz-object forms of the class toolkit — same generated truth, keyed by sz prop instead of class token.

function classifySzKey(key: string): { role: 'outer' | 'inner'; category: string } | undefined
function hasSz(sz: SzInput, selector: BoxSelector): boolean
function pickSz(sz: SzInput, selector: BoxSelector): SzObject
function omitSz(sz: SzInput, selector: BoxSelector): SzObject

stripSzProps(props) — Safe Prop Forwarding

Section titled “stripSzProps(props) — Safe Prop Forwarding”

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.

import { stripSzProps } from '@csszyx/runtime';
function Box({ sz, ...rest }: BoxProps) {
return <div {...stripSzProps(rest)} />;
}

verifyMangleChecksumAsync(expected, map?) — Integrity Check

Section titled “verifyMangleChecksumAsync(expected, map?) — Integrity Check”

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. verifyMangleChecksumAsync recomputes 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.

function verifyMangleChecksumAsync(
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
import { verifyMangleChecksumAsync } from '@csszyx/runtime';
const ok = await verifyMangleChecksumAsync(expectedChecksum);
if (!ok) {
// map missing, corrupted, or changed without updating the checksum
}

For edge/serverless environments that can’t afford the full runtime, the @csszyx/runtime/lite export provides _sz without hydration guards:

import { _sz } from '@csszyx/runtime/lite';

Lite exports: _sz, _sz2, _sz3, _szMerge, __szColorVar. No hydration, no checksums, no recovery.