Field Guide / advanced

Cascade layers: lower specificity without losing control

Cascade layers let teams control order explicitly so components do not need to win by becoming more specific.

Live demo

Specificity and cascade visualizer

Open demo page
CSS used in this demo
.tool {
  display: grid;
  gap: 1rem;
  max-width: 44rem;
  margin: 0 auto;
  padding: 1.5rem;
}

.tool h1 {
  margin: 0;
  font-size: 2rem;
}

label {
  display: grid;
  gap: 0.35rem;
  font-weight: 700;
}

input {
  min-height: 3rem;
  border: 2px solid #171717;
  border-radius: 6px;
  padding-inline: 0.75rem;
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
}

.score {
  display: inline-grid;
  width: fit-content;
  min-width: 9rem;
  place-items: center;
  border: 2px solid #171717;
  border-radius: 8px;
  background: #f8e8e2;
  padding: 0.75rem 1rem;
  font-size: 2rem;
  font-weight: 800;
}

Specificity fights are expensive because every fix raises the next override’s cost. Cascade layers give teams another lever: explicit order.

Without layers, a reset, component rule, utility class, and page override all compete in the same author origin. The later or more specific rule wins. With layers, you can decide that utilities always beat components, even when the utility selector is simpler.

The goal is not to avoid the cascade. The goal is to make the cascade visible enough that developers stop solving order problems with longer selectors.

Define the order once

@layer reset, tokens, base, layout, components, utilities;

That line is the contract. Rules in later layers win over rules in earlier layers when origin and importance are the same.

@layer components {
  .button {
    background: var(--button-bg);
    color: var(--button-text);
  }
}

@layer utilities {
  .bg-warning {
    background: var(--color-warning);
  }
}

The utility can stay simple. It does not need .button.bg-warning to win.

A practical layer stack

A maintainable stack is usually small. reset removes browser inconsistencies. tokens defines custom properties and design values. base styles elements. layout defines page-level primitives. components styles reusable UI. utilities provides deliberate final adjustments.

That order is not universal, but it is a useful default. What matters is that the order is declared once and reused consistently.

Layers are not a license to write chaotic selectors. A component rule with IDs, deep descendant chains, or repeated :not() selectors will still be hard to maintain. The layer controls broad ordering; the selector still needs to describe a local match.

Utilities and components need an agreement

Utilities are useful when they are allowed to do final adjustments. They are dangerous when they quietly become a second component system.

@layer utilities {
  .sr-only {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  .flow {
    display: grid;
    gap: var(--flow-space, 1rem);
  }
}

Good utilities are small and predictable. If a utility has many declarations and only makes sense for one component, it probably belongs in the component layer.

Keep unlayered CSS intentional

Unlayered author styles have higher priority than layered styles. That can be surprising if a random legacy file sits outside the layer system.

In a greenfield site, put authored CSS into layers or reserve unlayered rules for a small integration boundary. If a third-party library ships unlayered CSS, decide whether your project overrides should live unlayered too or whether you can import vendor CSS into a named layer.

!important still has its own ordering rules. Use it rarely and deliberately: accessibility utilities, forced hidden states, or integration boundaries where you are overriding hostile third-party CSS. If a component needs !important to beat another component, the layer order or selector design is probably wrong.

The production approach

For an existing stylesheet, do not create seven layers and move everything at once. Start by declaring the intended order, then move the safest groups first: reset rules, design tokens, and base element styles.

Next, move obvious components. Leave unclear rules in place until their ownership is understood. A rushed layer migration can preserve every old problem while adding a new priority system on top.

When a style does not apply, inspect layer order before escalating specificity. The losing rule may simply be in an earlier layer, the winning rule may be unlayered, or a custom property override may be clearer than a declaration override.

Use layers to clarify ownership

Layers are most valuable when they explain who is allowed to override whom. A page layout should not need a deeply specific selector to adjust spacing around a reusable component if the system has a clear layout or utilities layer. A component should not reach into every page context to defend itself. The layer order should make those responsibilities visible.

Keep third-party CSS decisions explicit. If a vendor stylesheet is imported unlayered, it can unexpectedly outrank your layered project styles. Importing vendor CSS into a named vendor layer gives your own layers a predictable place to override it. If the vendor CSS must stay unlayered, document that integration boundary and avoid spreading unlayered fixes throughout the project.

Cascade layers do not replace custom properties. Often the cleanest override is not another declaration in a later layer, but a variable set at the component boundary. Use layers for priority and custom properties for allowed configuration. When both are clear, components can stay low-specificity without becoming hard to theme.

Related guides: Custom properties, Stateful UI with :has(), and Grid vs Flexbox.

References