Override Deck without !important
You wrote a rule, it is not being applied, and the framework's rule is winning in dev
tools. The normal fix is a longer selector, and then !important, and then
!important on the framework's side too. Deck ends that argument before it
starts: put your CSS in a layer and it wins, however short your selector is.
The short answer
Wrap your CSS in @layer app.pages. That is it.
@layer app.pages {
.card { border-radius: 0; }
}
One class, no !important, and it beats .card in
deck.components even though the two selectors are identical. If you only
take one thing from this page, take that.
Deck declares the order before any rule exists, in
src/00-layers.css. Fifteen layers, eleven of them Deck's and four of them
yours, and later beats earlier:
@layer
deck.reset, deck.tokens, deck.type, deck.layout, deck.components,
deck.mobile, deck.motion, deck.effects, deck.utilities, deck.rtl, deck.print,
app.base, app.components, app.pages, app.overrides;
Why that works when specificity says it should not
The cascade decides a winner by asking a series of questions in order, and stopping at the first one that separates the candidates. Most people know the list as: importance, then specificity, then source order. Layers insert a step, and it is earlier than specificity:
| Asked | Question |
|---|---|
| 1st | Origin and importance — is one of them !important? |
| 2nd | Which layer is it in? Later layer wins. Unlayered beats every layer. |
| 3rd | Specificity — how detailed is the selector? |
| 4th | Source order — which came last in the file? |
Specificity is question three. If question two already separated them, three is never
asked. That is the whole mechanism: a single class in app.pages beats
.card .card-header .card-title in deck.components, not
because it is more specific — it is dramatically less — but because the argument was
settled a step earlier.
The counter-intuitive part, and the one that trips people who already know layers:
unlayered CSS beats every layer. A plain <style>
block with no @layer around it wins against all fifteen. That is by
design, so a page template can always have the last word, but it means putting your CSS
in a layer makes it weaker against your own unlayered CSS, not stronger. Pick
one convention and hold it.
Which of the four slots to use
All four beat all of Deck. They exist to order your own CSS against itself, which is the argument you will actually be having in six months.
| Layer | For | Yours? |
|---|---|---|
deck.reset |
Normalize and element defaults | Deck |
deck.tokens |
Custom properties only | Deck |
deck.type |
Headings, prose, links | Deck |
deck.layout |
Containers, stack, grid | Deck |
deck.components |
Buttons, forms, cards, charts | Deck |
deck.mobile |
Sheets, tab bar, icons | Deck |
deck.motion |
Transitions, entrances, view transitions | Deck |
deck.effects |
Gradients, 3D, surface treatments | Deck |
deck.utilities |
Single-purpose classes | Deck |
deck.rtl |
Direction fixes — must beat the utilities | Deck |
deck.print |
Paper | Deck |
app.base |
Your resets and element styles | yours |
app.components |
Your components, and your Deck overrides | yours |
app.pages |
Per-page and per-view tweaks | yours |
app.overrides |
The escape hatch, still not !important | yours |
app.base-
Element styles and token overrides. A different body font, a custom
--brand, your own<table>defaults. Things that should lose to your components. app.components- Your own components, and your permanent changes to Deck's. If every card in your product has square corners, that rule lives here, once. This is where most of your CSS belongs.
app.pages- One page or one view. The hero on the marketing home page, a dashboard that needs a wider rail. Rules that would be wrong applied everywhere.
app.overrides-
The escape hatch. Something has to win right now and you will come back to it. Keeping
it as a named layer rather than an
!importantmeans it is greppable — you can find every one of them in a second, which is not true of importance scattered through a codebase.
When your rule still is not winning
Work down this list. It is in order of how often each one turns out to be the answer.
-
Your stylesheet loads before Deck's. The order is declared
by Deck, but a layer only exists once something declares it. If your CSS is the first
to mention
app.pages, it creates the layer at that point — beforedeck.*have been declared — and yours ends up first, which means it loses. Load Deck first, always. This is the single most common cause. -
You are competing with an unlayered rule. An inline
styleattribute, or a<style>block with no@layer. Both beat every layer. Dev tools show unlayered rules with no layer badge. -
The other rule is
!important. Importance is question one, and it inverts the layer order — among!importantdeclarations, the earlier layer wins, so importance indeck.resetbeats importance inapp.overrides. Deck's source contains 101 of them, and they are not scattered: 84 are inside@media print, 7 insideprefers-reduced-motion, and of the remaining 10, six reset positioning, shadows and clipping on table cells when the data grid restacks into cards on a narrow screen, two implement the.no-motionopt-out, and two style an item being dragged and the gap it leaves. If one of those is beating you, you are printing, the reader has asked for less motion, you are inside a restacked data grid, or you are restyling a drag — and in each case the rule is meant to win. -
You are setting a property the element does not have.
gapon a non-flex parent,inseton a static element,widthon an inline one. Nothing to do with the cascade; the rule is winning and doing nothing. -
It is a custom property, and you set it too low.
--brand-600resolves on:root, so overriding--hue-brandon a card changes nothing. See the theming guide.
In Chrome and Firefox dev tools, the Styles pane groups rules by layer and names the layer above each block. If your rule is there and struck through, the layer above it is the one beating you, and that is the whole diagnosis.
Loading your CSS into a layer
If you would rather not wrap every file, assign it at import time:
@import url("components.css") layer(app.components);
@import url("dashboard.css") layer(app.pages);
Or declare the layer once at the top of your entry stylesheet and let the files fall into it. With a bundler, the same thing done in the right order:
/* app.css — loaded after deck.css */
@layer app.base {
:root { --hue-brand: 265; }
body { font-family: "Inter var", var(--font-sans); }
}
@layer app.components {
.card { border-radius: 0; }
.btn { text-transform: uppercase; letter-spacing: .04em; }
}
Deck ships each layer as a separate file under dist/layers/ as well as the
combined stylesheet. If you are only using the layout primitives and none of the
components, you can load fewer of them — but load them in the declared order, or you
are back to the problem in point one.
When not to reach for a layer
-
When a token would do it. Changing
--r-mdrestyles every rounded corner in the framework at once; a rule overriding.card { border-radius }fixes cards and leaves the popover, the modal and the input alone. Reach for the token first. -
When a variant already exists. Before writing
.card { box-shadow: none }, check whether that is.card-flush. Around a third of the overrides people write are a class Deck already ships. -
When it belongs on the element. A one-off width is a
styleattribute, and that is fine — a single inline declaration is easier to find and delete than a rule in a file three directories away. - When you are fighting a component's structure. If the override list is longer than about four declarations, you do not want that component with changes; you want your own, and Deck's tokens make writing one cheap.