Import cn, recipe, and their types from @teacss/classes. Install it as
an application dependency with bun add @teacss/[email protected]; teacss and its
build adapter remain development dependencies. These runtime helpers
compose class strings; the build still generates CSS only for valid tokens
discovered in scanned source. The
Classes Storybook guide
has interactive cn and recipe examples.
Merge class values with cn
import { cn } from "@teacss/classes";
cn("d:flex p:2", "p:4");
// "d:flex p:4"
cn("p:2@hover", "p:4");
// "p:2@hover p:4"
cn("icon:lucide-sun", "icon:lucide-moon");
// "icon:lucide-moon"cn accepts strings, nested arrays, conditional objects, and falsy branches.
The helper understands Standard property conflicts and shared icon
conflicts. It compares tokens with the same ordered conditions; changing
@hover to an unconditioned token does not remove the hover rule.
Importance and directional shorthand overlap also affect which token wins.
const classes = cn(
"d:inline-flex p-x:3",
compact && "p-x:2",
{ "bg-color:primary-200 text-color:primary-950": active },
callerClassName,
);cn is not a CSS validator. cn("p:4", "p:invalid") returns
"p:invalid"; the compiler may emit nothing for that value. See
Coverage when a merged class has no
style.
Define a one-element recipe
recipe holds a component’s base classes, variants, defaults, and compound
variants. A string defaults definition returns one merged class string:
import { recipe, type ClassProp, type VariantProps } from "@teacss/classes";
const badge = recipe({
defaults: "d:inline-flex p-x:2",
variants: {
tone: {
neutral: "bg-color:neutral-200 text-color:neutral-950",
accent: "bg-color:primary-200 text-color:primary-950",
},
},
defaultVariants: { tone: "neutral" },
});
badge({ tone: "accent", className: "p-x:3" });
type BadgeProps = VariantProps<typeof badge> & ClassProp;Define named elements
An object defaults definition returns a resolver per named element.
root has no special meaning; use names that match the rendered parts:
const notice = recipe({
defaults: {
frame: "d:grid gap:3 p:4",
icon: "w:4 h:4",
},
variants: {
compact: {
true: { frame: "p:2", icon: "w:3 h:3" },
},
},
});
const parts = notice({ compact: true });
parts.frame({ className: "p:3" });
parts.icon();Only the recipe definition’s base field is named defaults. Caller overrides
and compoundVariants[].className keep the name className; a compound may
target a slot map or broadcast through classKeys. Caller classes belong to a
resolver, not the named-element recipe call.
Use class conditions such as p:4@md for responsiveness rather than making
every breakpoint a recipe variant. The
Classes Storybook guide
also demonstrates compound variants and extension.