saucey

saucey compresses the sprawl of CSS into ~40 props across three modules — frame, layout, and text — authored as $frame / $layout / $text keys inside a css() call. A compile-time hook expands them into real Panda CSS, so tokens, conditions, atomic classes, and static extraction all come from Panda for free. You author intent; the compiler does the rest. Wrong is a type error.

Why it exists

CSS hands you 500+ properties and infinite ways to combine them — and "just write clean CSS" doesn't scale. The surface area is enormous, conventions get broken the moment they're inconvenient (by humans and LLMs), and a model will happily reach for whatever satisfies the immediate prompt regardless of the mess it leaves behind.

saucey removes the option to misbehave instead of asking nicely:

  • ~40 props, not 500. The essential vocabulary designers already think in — borrowed from the inspector panels of Figma, Framer, and Sketch — behind one function. Small enough to hold in your head, and to tell a model "only ever use this."
  • Wrong is a type error. Every value is a token or a constrained option; an off-system color or a typo doesn't compile. Docs are a suggestion; the compiler is a contract.
  • Reads like the Figma inspector. Styling reads top-to-bottom as intent — you review what it means, not how it works. Designers can contribute in the words they already use.
  • Free at runtime. It's a compile-time transform on top of Panda — tokens, conditions, atomic CSS, and static extraction are Panda's job; saucey adds zero runtime cost.
  • LLM-friendly. A tiny, constrained, typesafe surface a model can follow without inventing its own — the groundwork for agentic workflows that stay deterministic.
  • Human-friendly. Code reviews are easier to reason about, and tweaking the final 10% is read-it, find-it, change-it — not prompt-roulette.

Core concepts

Three modules, ~40 props. Every visual decision is one of three keys inside css():

Progressive disclosure — a prop takes its simplest form first and escalates only when you ask for more power:

Abstraction — saucey folds multi-prop CSS concepts into one intent, and blocks the footguns. One word in, the right CSS out:

$raw — the escape hatch. For the rare property no module covers (cursor, content, transition), drop to raw Panda under $raw — raw props live in that one labeled, greppable key, never scattered at the top level. So raw-CSS usage stays easy to spot in review: you stay on top of whatever debt you choose to take on, instead of it hiding in the styling.

The compile-time trick. saucey is a single Panda parser:before hook. Before Panda ever sees your source, sauceyToPanda finds every $frame / $layout / $text inside a css() call and rewrites it into valid Panda CSS — then Panda does the heavy lifting. Tokens work, conditions work, and the whole layer disappears at build time:

The other half is the runtime seam: connectSaucey binds css / cssv to a project's generated Panda types, so every value is strict against the real token and condition set:

A taste of the API

Author styles — modules are keys; conditions nest. css() returns a resolved object with a .toClasses() you drop on any element:

Compose fragmentscss() is variadic: each object resolves to CSS properties independently, then Panda merges those properties. Non-competing props stack; only a genuinely competing one is overridden (last wins):

Selective variantscssv defines a recipe for a closed set of author-chosen options (never reactive state — that's a data-* attribute + a condition):

Tokenizers — palette & font

The tokens the modules resolve against don't come from a static file — they come from tokenizers: createPaletteCollection and createFontCollection (from @saucey/tokenizers). Define your palettes and fonts once and each gives you two things — the Panda tokens the modules reference at build time, and a runtime hook to read and switch them live.

Palette — define each palette by its light/dark anchors (OKLCH start / center / end); the tokenizer expands them into a full shade ramp plus the semantic roles (accent, base, …). Dark mode is derived by swapping startend:

Font — define each face (source + variation settings) and the semantic roles (accent / base / code); the tokenizer wires the @font-face + size/weight tokens and the useFont hook. fluidScale builds a clamp() ramp between two breakpoints:

Wire the collection into Panda's preset. The same collection hands Panda the tokens the modules resolve against — the semantic aliases (accent, base, …), the CSS-variable values behind them, and @font-face — so fill: "accent.9" and size: "body" become real, statically-extracted tokens:

Read and switch at runtime. The boundary call creates a Provider and owns the state; anything below inherits it — resolve a shade, flip the mode, swap a palette, all live: