Skip to content
TeaCSS documentation

Classes

Compose TeaCSS class strings with cn and typed recipes without confusing merging with CSS generation.

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

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

ts
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:

ts
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:

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