shadcn-html / cascade-layers
Cascade Layers
Every component stylesheet wraps its rules in @layer components.
This is a single architectural decision that gives you guaranteed override power —
your own unlayered CSS takes precedence over layered component defaults, without !important.
What is @layer?
CSS Cascade Layers are a native browser feature that lets stylesheets declare which bucket their rules belong to. When two selectors have the same specificity, the browser uses layer order to decide which one wins. The key insight: for normal rules, unlayered styles beat layered styles, regardless of selector specificity.
How shadcn-html uses it
Every component CSS file — button, card, dialog, all of them — wraps all of its rules inside a single layer declaration:
The design tokens file (default-semantic-tokens.css) is intentionally not layered — token values sit at the top of the cascade so they're always available to both component and user styles.
The cascade order
Here's what the browser sees, from lowest to highest priority:
@layer components
All component CSS — button, card, dialog, etc. Lowest priority.
default-semantic-tokens.css
Design tokens (:root custom properties). Unlayered — always available.
Because component rules live inside @layer components and your CSS is unlayered,
the browser gives your styles higher cascade priority automatically. You never need
to out-specify a component selector or reach for !important just to beat a default component rule.
Note: Tokens and your styles are both unlayered — between them, normal specificity and source order apply.
Tokens don't compete with your component overrides because they only define :root custom properties, not component selectors.
Overriding component styles
Write your overrides in a regular, unlayered stylesheet. They'll always take precedence.
Include your override file after the component stylesheets:
Using with other CSS frameworks
If you're mixing shadcn-html components with another CSS framework that uses its own @layer declarations,
you can declare an explicit layer order at the top of your main stylesheet to control priority:
Without an explicit order declaration, layers are ordered by first appearance in the stylesheet. The explicit declaration gives you full control regardless of import order.
Why not just write unlayered CSS?
Traditional CSS libraries (Bootstrap, Bulma) ship unlayered styles. That creates problems when you try to customize them.
!important or specificity hacks.
Predictable cascade
Component internals can use any selector complexity — it won't leak into your layer.
Composable layers
Works cleanly alongside other libraries or your own @layer declarations.
Future-proof
@layer is a W3C standard, built into every modern browser. Not a framework convention.
A note on !important: Cascade layers reverse priority for !important rules —
an !important declaration inside @layer components actually beats an unlayered !important.
This system never uses !important, but be aware of this if you're mixing in third-party CSS that does.
Why not @scope or Web Components?
Shadow DOM creates a styling boundary that makes theming painful — external CSS can't reach inside, so every component needs its own token-plumbing or ::part() surface. SSR is complex, framework interop suffers, and devtools show an extra layer of indirection. @scope strips context from class names, making selectors ambiguous outside their block. Both add abstraction that makes the system harder to read, generate, and modify — for humans and AI alike — so we use neither.
<button class="btn" data-variant="outline"> is just HTML. No custom element registration, no shadow root, no template cloning. Nothing to misunderstand, nothing to get wrong.
Theming without boundaries
Shadow DOM blocks external stylesheets from reaching component internals. With plain HTML, your CSS custom properties and overrides apply everywhere — no ::part() or adopted stylesheets required.
Flat, visible DOM
Shadow DOM hides structure behind custom elements and adds another inspection boundary. DevTools can still reveal open shadow roots, but plain HTML keeps every component directly visible in the main DOM tree and easier for tools to reason about.
Self-documenting class names
A class like .card-header carries its meaning in the name — it can be generated without knowing the surrounding markup. With @scope, a generic .header is ambiguous outside its block.
The cascade delivers context for free
.card-title inherits font and color tokens from its parent .card automatically. This inheritance is the context — the more of it embedded in the structure, the more reliably tools and AI produce correct output.
Simplicity isn't a limitation — it's a deliberate design choice. Every pattern in this system was chosen not just to avoid complexity, but to maximize theming flexibility, tooling compatibility, and the context available to AI.
Browser support
@layer is supported in all modern browsers. No polyfill needed.
See caniuse.com/css-cascade-layers and the MDN @layer reference for full details.