Cascade Layers (@layer)

Organize your CSS into ordered layers. Solve the specificity war by deciding which category of styles wins.

CSS4 min readConcept 38 of 43

Fixing the specificity war

Historically, the CSS cascade relied heavily on specificity. An ID selector (#button) would always defeat a class selector (.btn), even if the class was written much later in the file.

This led to 'specificity wars', where developers had to write increasingly complex selectors (or use !important) just to force their styles to apply.

Cascade Layers (@layer) introduce a new level to the cascade. They allow you to group styles into named layers and explicitly declare the priority order of those layers, completely bypassing traditional specificity rules.

Predictable style overrides

By defining layer order up front (e.g., @layer reset, base, components, utilities;), the browser guarantees that styles in utilities will always override styles in components.

Even if a rule in the components layer uses a heavy ID selector, a simple class selector in the utilities layer will win, because the layer priority is evaluated before selector specificity.

This makes integrating third-party libraries trivial: you simply load the library into a low-priority vendor layer, and your own components layer will cleanly override it without fighting its specificity.

Defining and populating layers

First, establish the order. The layer listed last has the highest priority: @layer reset, theme, components;.

Then, populate the layers by wrapping your CSS rules in @layer blocks. @layer theme { .card { background: dark; } } .

You can also import entire external stylesheets directly into a layer using @import url('theme.css') layer(theme);.

Try it

Reorder the layer declaration below. Watch how the winning style completely ignores traditional specificity.

Layer Order vs Specificity

/* 1. Define the layer order (last layer wins) */
@layer
;
/* 2. Populate the Base layer (Low priority in default order) */
@layer base {
  /* High specificity ID selector */
  #hero-card { color: blue; }
}
/* 3. Populate the Utilities layer (High priority in default order) */
@layer utilities {
  /* Low specificity class selector */
  .card { color: red; }
}

I am the card.

The utilities layer wins.

Even though #hero-card has higher specificity, layer priority is evaluated first. The layer declared last takes precedence.

Check yourself

Pick an answer to lock it in, then read why. Getting one wrong is part of how it sticks.

  1. 1Given `@layer base, utilities;`, if `.card` in `utilities` sets `color: red` and `#hero-card` in `base` sets `color: blue`, which color wins?
  2. 2What happens if a style is written without an `@layer` block (unlayered)?
  3. 3How can you load an external stylesheet directly into a specific layer?

Remember this

  • Cascade Layers bypass specificity. A rule in a higher-priority layer always wins.
  • Layer priority is defined by declaration order. The last declared layer wins.
  • Normal CSS written outside of any layer (unlayered) will defeat all layered styles.
  • You can import external stylesheets directly into a layer using @import.

Done with this concept?

Mark it complete to track your progress. No login needed.