How Salty CSS Eliminates the CSS Specificity War with Cascade Layers and Deterministic Hashing
Scoping & Specificity
Scaling CSS safely has always been a battle on two fronts. First, you have to achieve isolation (scoping) so your styles don't accidentally leak and break the rest of the application. Second, you have to manage overrides (specificity) so you can intentionally alter a component without triggering an unwinnable arms race of chained selectors and !important tags.
The frontend ecosystem has tried to solve this in various ways. CSS Modules and Vanilla Extract gave us strict local scoping, but made targeting global elements or nested children rigid and clunky. Panda CSS brought powerful build-time extraction but forced explicit boilerplate (like requiring & prefixes for everything) that steepens the learning curve.
Salty CSS takes a different approach. It pairs the beloved, fluid selector ergonomics of Stitches with the architectural absolute truth of native CSS Cascade Layers. This page explains how Salty CSS scopes your styles effortlessly and completely eliminates the traditional CSS specificity war.
The Ergonomics of Isolation
Salty CSS generates a deterministic, locally scoped hash class (e.g., TzHVd) for every component or class definition. But unlike older scoping solutions that make stepping outside that hash painful, Salty CSS embraces natural, standard CSS nesting.
Natural Nesting & Responsive Scoping
Whether you are using pseudo-selectors (&:hover), complex chained states (&:not(:disabled):active), combinators (& > svg), or custom data attributes (&[data-state="open"]), the syntax remains fluid.
Furthermore, this scoping automatically applies to responsive design. When you nest an @media or @container query directly inside your component definition, the compiler guarantees the rules remain strictly isolated to that specific element's hash.
And importantly, this entire ergonomic surface isn't locked behind the styled() component wrapper. If you just need a raw class string to pass into a third-party library, the lightweight className() generator supports the exact same nesting parity.
Component Targeting (Hash Interpolation)
Salty CSS treats components as first-class selectors. Because styled() components and className() functions expose their underlying hash string, you can interpolate them directly into your selectors. This allows you to style child components contextually without relying on brittle, untyped DOM structures:
import { Icon } from "./icon.css"; export const Button = styled("button", { base: { padding: "1rem", // Target the specific Salty component inside this button [`& ${Icon}`]: { opacity: 0.8, transition: "opacity 0.2s", }, "&:hover": { [`& ${Icon}`]: { opacity: 1 }, }, }, });
Stable Classes for External Targeting
Generated hashes are great for encapsulation, but they are terrible for external targeting or end-to-end testing.
Unlike libraries that force you to guess the hash or manually attach standard classes in your JSX, Salty CSS supports a native className option directly inside the definition.
export const Card = styled("article", { className: "salty-card", // Stable class appended alongside the hash base: { padding: "1rem" }, });
The rendered DOM element receives both the encapsulated hash and your custom string. This class remains perfectly stable even if the consumer extends the component or appends additional classes in their own JSX. It provides a reliable hook for legacy CSS integrations, global overrides, or QA testing frameworks.
Defeating the Specificity War
Once your styles are scoped, the next challenge is overriding them. In traditional CSS, if two selectors target the same element and have the same specificity weight, source order dictates the winner. In a bundled, chunked component architecture, guaranteeing source order is practically impossible.
Salty CSS completely abandons source-order dependency. Instead, it leverages native CSS Cascade Layers (@layer) to guarantee predictable, deterministic overrides regardless of how complex your application gets.
All rules are emitted into a strict, predefined layer hierarchy:
@layer imports, reset, global, templates, fonts, l0, l1, l2, l3, l4, l5, l6, l7, l8;
Rules in l1 will always override rules in l0, even if the l0 selector is technically more specific.
Auto-Bumping: Safe Component Extension
When you create a standard component using styled('button', {...}), it defaults to priority 0 and lands in the l0 layer.
The magic happens when you extend an existing component. If you write styled(Button, {...}), the Salty compiler automatically detects the extension and bumps the new component's priority to 1 (landing in l1).
// Lands in @layer l0 export const Button = styled("button", { base: { background: "gray", padding: "1rem" }, }); // Lands in @layer l1 — guaranteed to win against Button's base styles. export const PrimaryButton = styled(Button, { base: { background: "blue" }, });
You never have to artificially inflate your selectors to force a style to apply. The layer architecture guarantees the extending component always wins.
Manual Priority Control
Sometimes, you need to manually intervene. You can pass an explicit priority property to any styled component or class name generator to force it into a specific layer. Think of priority as z-index for the CSS cascade.
export const UtilityOverride = styled("div", { priority: 2, // Forces this rule into @layer l2 base: { color: "red" }, });
Beyond creating high-priority utility classes, explicit priority is the ultimate fix for hashing collisions. In large codebases, generated hash class names can occasionally shift in alphabetical source order during the bundling process. If you encounter a random styling regression because a previously "winning" rule suddenly loads earlier in the file, manually bumping the priority instantly resolves the tie without resorting to hacks.
The !important Trap and Zero Specificity
Salty CSS is pragmatic: it does not strip, warn on, or rewrite !important. If you write it, the compiler emits it. Developers use what is familiar, and sometimes you just need to force a style through a third-party DOM boundary.
However, you should be aware of a fundamental quirk in native CSS: !important inside cascade layers reverses precedence.
In regular CSS layers, l1 beats l0. But if both layers use !important, the earlier layer (l0) wins. This inverted logic often breaks mental models and leads to deeply confusing bugs. Prefer raising the layer priority instead.
The Better Escape Hatch: CSS Variables & the style Prop
If you need a per-instance override that guarantees victory without breaking the layer architecture, use CSS Custom Properties. Salty CSS treats the native React style prop as a first-class citizen, allowing you to pass arbitrary CSS variables to drive your component internals:
// Inside your styled component: // width: "var(--custom-width, 100%)" // At the call site: <Button style={{ "--custom-width": "50%" }}>Submit</Button>
Inline styles sit entirely outside the CSS layer cascade and will always win, giving you a clean, type-safe "nuclear option" when you need it.
Zero Specificity: anyOfVariants
While cascade layers handle strict overrides, sometimes you need the exact opposite: a rule that gracefully yields to anything else.
Salty CSS offers three variant structures:
-
variants: Triggers on a single prop match. -
compoundVariants: Triggers only when X AND Y match. -
anyOfVariants: Triggers when X OR Y match.
To make anyOfVariants work without polluting your component with overly aggressive rules, the compiler wraps them in the native :where() pseudo-class:
anyOfVariants: [ // Emitted as: .hash:where(.intent-danger) { ... } { intent: "danger", css: { fontWeight: "bold" } }, ];
Because :where() carries zero specificity, anyOfVariants allows you to define broad, shared logic across multiple variant combinations while ensuring that any standard variants rule targeting the same property will effortlessly override it.