Cascade Layers & @scope

As stylesheets grow, specificity battles become the main source of pain. Cascade layers (@layer) let you decide which groups of styles win, regardless of specificity. @scope limits styles to a part of the page. Together they bring order to large projects.

The problem

Try it yourself
Result

A highly specific rule — perhaps from a third-party theme — beats your simpler utility class, and the usual "fixes" are more specificity or !important.

Defining layers

Try it yourself
Result

The first line declares the layer order. A rule in a later layer always wins over one in an earlier layer — specificity only matters within a layer. So the simple .button-primary in utilities beats the id selector in base.

Layer order rules

  • Order is fixed by the first time each layer name appears — that's why declaring them all up front is good practice.
  • Unlayered styles beat all layered styles. Code you haven't put in a layer has the highest priority.
  • For !important declarations the order reverses: important styles in earlier layers win.

Putting third-party CSS in a layer

CSS import-layer.css
@layer vendor, components, utilities;

@import url("https://cdn.example.com/framework.css") layer(vendor);

@layer components {
  .card { border-radius: 12px; }
}

Importing a framework into a low-priority layer means your own styles override it without specificity hacks — one of the most practical uses of layers.

Nested layers

CSS nested-layers.css
@layer components {
  @layer buttons {
    .btn { padding: 8px 16px; }
  }
  @layer cards {
    .card { padding: 16px; }
  }
}

@layer components.buttons {
  .btn-small { padding: 4px 10px; }
}

Layers can contain sub-layers, referenced with dot notation.

A typical layer architecture

LayerContents
resetNormalising browser defaults, box-sizing
vendorThird-party CSS
baseElement styles: typography, links, forms
layoutPage structure
componentsCards, buttons, navigation
utilitiesSingle-purpose helper classes that should always win

@scope

Try it yourself
Result

@scope (.card) limits the rules inside to elements within .card. Inside, :scope refers to the scope root itself.

Scoping with a lower boundary

Try it yourself
Result

The to (…) clause creates a "donut scope": styles apply inside .article but not inside nested .comments. This is something ordinary descendant selectors can't express.

Scope proximity

When two scoped rules have the same specificity, the one whose scope root is closest to the element wins. That makes nested themes work naturally — a light card inside a dark section takes the light styles. @scope is supported in Chromium browsers and Safari, and arriving in Firefox.

Try it yourself

Try it yourself
Result