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 fragments — css() 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 variants — cssv 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 start ↔ end:
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: