lynstack recipe
Variants in, styles out.
npm install @lynstack/class-recipeimport { cva } from "@lynstack/class-recipe";
const button = cva({ base: "rounded-md", variants: { tone: { neutral: "bg-gray-100 text-gray-900", danger: "bg-red-600 text-white", }, size: { sm: "h-8 px-3", md: "h-10 px-4" }, }, defaultVariants: { size: "md" },});
button({ tone: "danger" });// => "rounded-md bg-red-600 text-white h-10 px-4"Packages
Pick the package for your values
Each package makes recipes for one kind of value. They are all built on the same engine, so their variants, cache, and types work the same way.
- @lynstack/class-recipe1.2.0Class namescva for one element, sva for several, and cx, a drop-in replacement for clsx. Joins with twMerge when you need it.
- @lynstack/native-recipe1.1.0React Native stylesStyle recipes and slot style recipes for React Native, built from theme tokens when you need them, which return the same frozen style for the same variants.
- @lynstack/recipe1.2.0Core engineThe engine: define how a kind of value combines, and get recipes and slot recipes that select, cache, and type it.
Agent skills
Teach your coding agent the recipes
class-recipe and native-recipe ship agent skills that teach Claude Code, Codex, Cursor, and other agents to write recipes the way these docs recommend.
npx skills add lynstack/recipe --skill class-recipenpx skills add lynstack/recipe --skill native-recipeWhy recipe
Compiled once, a lookup after that
Fast
9× the calls per second of class-variance-authority. A recipe compiles its config once and caches each selection, so a call is a lookup.
Type-safe
Variants, options, and slots are inferred from the config. An unknown option is a type error, and a variant without a default is required.
Your join
Bring twMerge or any join of your own. It runs once per selection, not on every call.
One engine
Every package shares the variants, the cache, and the types of one engine, so a recipe behaves the same whatever it returns.