BarefootJS uses CSS Cascade Layers to guarantee that user-supplied classes always override component base classes — without runtime JS, without merge functions, and without worrying about generation order.
#The Problem
When a component defines base classes and a user passes override classes, both have equal CSS specificity. The winner depends on which CSS rule appears later in the stylesheet — which in turn depends on the order the CSS toolchain happens to generate them.
// Component defines base classes
<button className="bg-primary text-white">
// User wants to override the background
<Button className="bg-red-500">If bg-primary is generated after bg-red-500 in the CSS output, the user's override silently fails. This is fragile and order-dependent.
#The Solution
CSS Cascade Layers provide a spec-level mechanism for controlling style priority. Styles in a named @layer always lose to un-layered styles, regardless of specificity or source order.
BarefootJS puts component base classes into @layer components. User-supplied classes remain un-layered. The cascade guarantees the user wins:
/* Layer ordering: lowest → highest priority */
@layer preflights, base, shortcuts, components, default;Un-layered styles (user overrides) always beat any layer — this is defined by the CSS spec, not by any toolchain convention.
#How It Works
The compiler's cssLayerPrefix option prefixes component base classes at compile time.
#Source
const baseClasses = 'inline-flex items-center bg-primary text-primary-foreground'
export function Button({ className = '', children }) {
return (
<button className={`${baseClasses} ${className}`}>
{children}
</button>
)
}#Compiled Output (with `cssLayerPrefix: 'components'`)
const baseClasses = 'layer-components:inline-flex layer-components:items-center layer-components:bg-primary layer-components:text-primary-foreground'
export function Button({ className = '', children }) {
return (
<button className={`${baseClasses} ${className}`}>
{children}
</button>
)
}#Generated CSS
The CSS toolchain (e.g., UnoCSS) sees the layer-components: prefix and emits those classes inside @layer components:
@layer components {
.layer-components\:bg-primary { background-color: var(--primary); }
.layer-components\:text-primary-foreground { color: var(--primary-foreground); }
/* ... */
}
/* User classes — un-layered, always win */
.bg-red-500 { background-color: #ef4444; }#Cascade Resolution
<Button className="bg-red-500">
Applied classes:
layer-components:bg-primary → @layer components (lower priority)
bg-red-500 → un-layered (higher priority)
Result: bg-red-500 wins. Always.#Key Properties
- Zero runtime cost — Prefixing happens at compile time. No JS runs in the browser to merge classes.
- Works with any CSS tool — The
layer-components:prefix convention is supported by UnoCSS. Any tool that supports CSS Cascade Layers can use this approach. - No merge function needed — The CSS cascade handles class conflict resolution natively.
- Language-independent — The prefixing is applied to the IR, so Go, Rust, and Node adapters all benefit equally.
- Preserves both classes — Both classes remain in the DOM. DevTools shows exactly what was applied and what was overridden.
#Configuration
Enable CSS layer prefixing by setting cssLayerPrefix in the compiler options:
compile(source, {
cssLayerPrefix: 'components',
// ...
})For the CSS side, declare the layer order at the top of your global stylesheet:
@layer preflights, base, shortcuts, components, default;See the UnoCSS integration guide for a full setup example.