Why Salty CSS Chose Semantic Components Over Atomic CSS and Primitives Over Pre-baked UI Kits
Design Philosophy
Every styling library is a collection of tradeoffs. In recent years, the frontend ecosystem has optimized heavily for the absolute smallest possible CSS file size, sometimes at the direct expense of developer experience (DX) or code readability.
Salty CSS makes a different set of tradeoffs. It prioritizes a TypeScript-first developer experience — flawed on its own terms, but those are the flaws I'd rather live with — alongside semantic component structures and out-of-the-box design system primitives. I'd rather spend a few extra kilobytes of heavily cached, gzipped CSS than spend developer time — of the two, developer time is by far the more expensive resource.
This page lays out the philosophy behind those tradeoffs: why Salty CSS reaches for semantic components instead of the atomic paradigm for its target use cases, and why it ships primitives rather than a pre-baked UI kit.
Why Not Atomic CSS?
Atomic (or utility-first) CSS has dominated the conversation recently. Tools like Tailwind CSS generate a massive set of single-purpose classes, while build-time extractors like Panda CSS parse your AST to generate atomic classes on the fly.
Let me be intellectually honest: when atomic CSS is done well, it really does produce a smaller global stylesheet — that's its whole pitch, and it's a fair one. If you're building a lean, repetitive SaaS dashboard, atomic extraction is a genuinely great architectural choice, and I'd point you toward it without hesitation.
But Salty CSS wasn't built for lean SaaS dashboards. It was built for design-led, highly dynamic websites — the kind with complex, unique visual effects, dynamic content blocks (like headless-CMS layouts), and rich variants. In that world, the atomic model starts to strain in two ways:
-
The HTML soup. Utility-first approaches tend toward bloated, hard-to-read markup. Finding the actual DOM structure underneath forty utility classes is a real cognitive tax — and it gets worse, not better, as the designs get more ambitious.
-
Scoping deeply nested, dynamic styles. A lot of design-led work is about crafting the nicest-looking atoms and seeing how they connect — and the same atom often has to render differently depending on context — a dozen or more distinct configurations in a typical headless-CMS setup. Expressing all of that through atomic utilities gets awkward fast. Salty is happy to scope deeply nested style objects right inside a single
styledcall, which is exactly the shape this kind of work takes.
Semantic Scoping & Mitigation
Salty CSS chooses semantic, component-level scoping. When you write styled("button", { ... }), you get a single, predictable hash class that represents that exact visual state.
The honest tradeoff is global stylesheet size, and Salty mitigates it through compiler architecture rather than pretending it away:
-
Hash deduplication: If two completely different components generate the exact same style object, they produce the same hash and are deduplicated in the final CSS automatically.
-
Component-level import strategy: By configuring
importStrategy: 'component', you can split your CSS so the browser only loads the stylesheets needed for the rendered route, rather than one monolithic global file.
On the roadmap: auto-extraction. There's a planned compiler optimization for production builds that would narrow the gap further: analyze the final stylesheet, find highly repeated property combinations (e.g. display: flex; align-items: center; justify-content: center;), and extract them into shared, semi-atomic rules automatically. You'd keep the component-level DX while the compiler quietly trims the final CSS weight. I can't put a firm timeline on it, but the hope is to land an experimental version to test during V1.
Primitives over Pre-baked UI Kits
Salty CSS is not a UI kit. It does not give you a pre-styled <Button> or <Modal>.
Pre-baked UI kits are great for prototyping, but they almost always become a liability the moment you need to match a bespoke Figma design exactly. Overriding a highly opinionated component library often takes more CSS than building the component from scratch would have.
If a UI kit is genuinely what you want, that's a fair call — and these days I'd happily point you at shadcn/ui. It sidesteps the override problem by living inside your own codebase rather than as an opaque dependency, the components themselves are clean, and it's popular for good reason. It's a different category of tool from Salty entirely.
Salty CSS is designed to be the foundational layer for building your own UI kit.
Shake It, Baby
When you build a bespoke design system, you quickly find you need more than a class-name generator. You need tools for fluid scaling, color manipulation, and typography. Historically that meant bloating your package.json with external dependencies like polished, or hand-writing fiddly calc() math.
Salty brings these essential primitives natively — and ships them as one cohesive package. There's no constellation of add-ons to install separately, the way some libraries split out their recipe and utility packages into things you assemble yourself. Modern tree-shaking means there's no real cost to keeping it together, and one good API is far easier to learn than five small ones; you just discover features as you go. Everything below is strictly build-time, so none of it ships to the browser:
-
Fluid viewport clamps. The
defineViewportClamputility replaces rigid, stair-stepped media queries with smooth fluid scaling: you set a reference screen size and the compiler translates your values into native CSSclamp(). You can even wire it intodefaultUnit— e.g.defaultUnit: 'viewport-clamp:1920'— so a raw number likepadding: 16compiles straight into a fluid clamp against a 1920px reference, scaling between automatic 0.5× (min) and 1.5× (max) bounds. Cool, and — I'll admit — pleasantly lazy. -
Build-time color manipulation. The
color()function lets you chain transforms like.lighten(),.darken(),.alpha(), and.mix()directly in your style objects. Because it runs during compilation, you get a plain CSS string in the browser without shipping a color-manipulation library in your client bundle. (Under the hood it does lean on the third-partycolorlibrary — a build-time dependency only.) -
Variant-enabled templates. With
defineTemplatesyou can codify typography — and more — as reusable, variant-aware presets. Text styles are the clearest example: the CSSfontshorthand has existed forever, but it never covered letter-spacing, color, or auto-inserting a before/after icon. A text-style template can, and using it is a one-liner liketextStyle: "heading.large"that the very next line in your style sheet can override directly if you need to.
Think less "batteries included," more slowly-cooked Ragu: a small set of carefully chosen, high-quality ingredients — San Marzano tomatoes, beef chuck, osso buco for the marrow, and an honestly heroic amount of salt — rather than a pantry stocked with every spice known to man. Salty ships the primitives you'll actually reach for and then trusts you to bring your own. You can write custom utilities that work anywhere, and because the compiler evaluates your style files at build time, any property's value can even be an asynchronous function. The limit is roughly "reasonable code safety," not a fixed menu.
That's the whole idea: Salty keeps your design system cohesive, strictly typed, and completely zero-runtime, then gets out of your way. We provide the salt; you cook the meal. 🧂
Where to go next
- Why Salty CSS Exists — the story and positioning behind these tradeoffs.
- The Styled API — the semantic component factory this page argues for.
- Scoping & Specificity — how the hashing, deduplication, and import strategy actually work.
- Provider-less Theming — design tokens without a runtime.
- Viewport Clamp and Color Function — the build-time primitives in depth.