CSS and Layout skill 2 of 3

Cascade Layers SKILL.md: Fix Specificity, !important and Overrides

The Cascade Layers skill makes Claude, Cursor or another AI agent find why one CSS rule loses to another and fix it without piling on !important. A read-only checker flags unlayered imports, late layer order and !important inside layers, a resolver names the winning declaration, and the agent migrates in small approved batches.

cascade-layers/SKILL.md, version 1.0.0, 3,146 words, plus scripts/cascade_check.py.

An agent fixes CSS conflicts in this order: run a read-only checker over the stylesheets in load order, find which declaration actually wins for the failing element with a resolver, decide whether cascade layers are worth adopting, then migrate in small approved batches and confirm each result in a browser.

It works on plain CSS, bundler output and <style> blocks. Facts and measurements sit in references/appendix.md, migration code in references/migration.md.

When to use this skill, and when not to

Use it when a rule does not apply, when a vendor or framework style beats yours, when !important is spreading, or when you want to set up or repair @layer.

RequestUse instead
Overflow, shrinking, grid tracksflexbox-grid-layout.md
Component width responding to its containercontainer-queries.md
@scope, CSS Modules, shadow DOM designOut of scope for this release
CSS-in-JS class order at runtimeThe library's own docs. This skill reads static CSS
Class naming schemes such as BEMOut of scope

Guardrails

  1. Propose, then change. Report and plan first. Edit only after the user approves a batch, on a branch under version control. Never push, merge or deploy.
  2. Never edit vendor files. Do not change files in node_modules, vendor folders or CDN copies. Wrap them in a layer at the import instead.
  3. No credentials in output. Stylesheets can hold signed url() values or tokens in query strings. The checker truncates values to 30 characters, the browser snippet to 70. Do not paste url(...) values into reports.
  4. Batches stay small. At most 1 file or 20 rules per batch (defaults), each shown as a diff.
  5. Nothing is invented. Thresholds are labelled defaults. Source facts carry a date. Version-dependent facts carry verify before relying on this.
  6. Layers are not a fix for a small project. Adopt them only past the thresholds in step 3.
  7. Refuses to: add !important, win with a style attribute, add an id selector, reorder layers by guesswork, or claim a result without the browser check in step 7.

Inputs the agent must have before starting

Ask for every missing item. Do not assume.

InputAsk exactly
Stylesheets in load order"List every stylesheet in the order the page loads it, including @imports, framework CSS and any CDN files."
The failing case"Which element, which property, what value do you expect and what value do you get?"
Third-party CSS"Which stylesheets can you not edit?"
Framework"Do you use Tailwind, Bootstrap or another framework that already declares layers? Which version?"
Browsers"Which browsers must work? Layers are Baseline widely available (MDN, March 2022)."
How to run the page"How do I open it locally?"
Approval"May I edit on a branch in batches, showing each diff first?"

Raw SKILL.md file

.md

This is the exact file agents read, including the credit header and licence notice. Save it as cascade-layers/SKILL.md.

---
name: cascade-layers
description: "Audits and fixes CSS that loses to other CSS: specificity wars, !important chains, vendor or Tailwind styles that win, and cascade layer order. Runs a read-only checker for unlayered imports, late layer order statements, !important inside layers, id selectors and high specificity, plus a resolver that ranks which declaration wins, then plans a layer migration in approved batches. Use for CSS not applying, @layer, specificity, overriding third-party styles. Do NOT use for layout overflow, container queries, CSS-in-JS runtime bugs or class naming schemes."
license: CC-BY-4.0
metadata:
  author: Afeez Adebayo
  author_url: https://afeezadebayo.com
  author_linkedin: https://www.linkedin.com/in/afeez-adebayo/
  author_x: https://x.com/za_icon
  source: https://afeezadebayo.com/skills-md/frontend-development/css-layout/cascade-layers/
  category: frontend-development
  subcategory: css-layout
  version: "1.0.0"
  created: "2026-10-25"
  updated: "2026-10-25"
---

# Cascade Layers and Specificity: Why a Rule Loses and How to Fix It

An agent fixes CSS conflicts in this order: run a read-only checker over the stylesheets in load order, find which declaration actually wins for the failing element with a resolver, decide whether cascade layers are worth adopting, then migrate in small approved batches and confirm each result in a browser.

It works on plain CSS, bundler output and `<style>` blocks. Facts and measurements sit in `references/appendix.md`, migration code in `references/migration.md`.

## When to use this skill, and when not to

Use it when a rule does not apply, when a vendor or framework style beats yours, when `!important` is spreading, or when you want to set up or repair `@layer`.

| Request | Use instead |
|---|---|
| Overflow, shrinking, grid tracks | `flexbox-grid-layout.md` |
| Component width responding to its container | `container-queries.md` |
| `@scope`, CSS Modules, shadow DOM design | Out of scope for this release |
| CSS-in-JS class order at runtime | The library's own docs. This skill reads static CSS |
| Class naming schemes such as BEM | Out of scope |

## Guardrails

1. **Propose, then change.** Report and plan first. Edit only after the user approves a batch, on a branch under version control. Never push, merge or deploy.
2. **Never edit vendor files.** Do not change files in `node_modules`, vendor folders or CDN copies. Wrap them in a layer at the import instead.
3. **No credentials in output.** Stylesheets can hold signed `url()` values or tokens in query strings. The checker truncates values to 30 characters, the browser snippet to 70. Do not paste `url(...)` values into reports.
4. **Batches stay small.** At most 1 file or 20 rules per batch (defaults), each shown as a diff.
5. **Nothing is invented.** Thresholds are labelled defaults. Source facts carry a date. Version-dependent facts carry **verify before relying on this**.
6. **Layers are not a fix for a small project.** Adopt them only past the thresholds in step 3.
7. **Refuses to:** add `!important`, win with a style attribute, add an id selector, reorder layers by guesswork, or claim a result without the browser check in step 7.

## Inputs the agent must have before starting

Ask for every missing item. Do not assume.

| Input | Ask exactly |
|---|---|
| Stylesheets in load order | "List every stylesheet in the order the page loads it, including `@import`s, framework CSS and any CDN files." |
| The failing case | "Which element, which property, what value do you expect and what value do you get?" |
| Third-party CSS | "Which stylesheets can you not edit?" |
| Framework | "Do you use Tailwind, Bootstrap or another framework that already declares layers? Which version?" |
| Browsers | "Which browsers must work? Layers are Baseline widely available (MDN, March 2022)." |
| How to run the page | "How do I open it locally?" |
| Approval | "May I edit on a branch in batches, showing each diff first?" |

## Workflow

### Step 0: Confirm scope

List the files in load order. Minified input is fine for the checker, but fixes go in source files. One failing case at a time.

### Step 1: Run the checker

```
python3 scripts/cascade_check.py vendor.css app.css | head -40
```

Read only its output. Do not read the script. Pass files in load order. `--max-spec 3` changes the class-level threshold. Exit code 1 means an error. The last two lines give the layer order and counts. Each code has one fix, in this table.

| Code | Level | Meaning | Fix |
|---|---|---|---|
| `import-no-layer` | warn | An `@import` without `layer()` is unlayered, so it beats every layered rule | `@import url(x.css) layer(vendor);` |
| `import-after-rules` | error | `@import` after a style rule is invalid and ignored | Move it above the rules, after any `@layer` statement |
| `no-order-statement` | warn | Layer blocks appear with no earlier `@layer a, b;`, so order follows first appearance | Add one statement at the top of the first stylesheet loaded |
| `order-statement-late` | warn | A statement names layers already ordered; it cannot move them | Delete it and put the full order in the first statement |
| `utilities-not-last` | warn | A later layer beats utilities | Move `utilities` to the end of the order, before any `*important*` layer |
| `important-in-layer` | warn | Important order is reversed: an earlier layer's `!important` beats a later one | Step 6 |
| `anonymous-layer` | info | An unnamed layer cannot be extended or reordered by name | Name it |
| `id-selector` | warn | An id selector, specificity (1,0,0) or more | Class selector |
| `high-specificity` | warn | Class-level specificity of 3 or more | Flatten the selector, or place it in a later layer |

### Step 2: Find what wins for the failing case

1. In the browser, run the matched-rules snippet (Templates) on the element. It lists each matching rule with its layer. Cross-origin stylesheets are skipped by the browser, so read those in DevTools.
2. Run the resolver with the selectors from that list:

```
python3 scripts/cascade_check.py vendor.css app.css --rank background --sel ".btn" --sel ".bg-brand"
```

The first line starts `WINS` and shows layer, specificity and importance. It models layers, importance, specificity and source order. It does not model style attributes, animations or selectors outside the files you passed.
3. Confirm with `getComputedStyle(el).background` in the browser. If the resolver and the browser disagree, a style attribute, an unscanned file or a transition is involved. Stop and find it before editing.

### Step 3: Decide whether to adopt layers

Read the counts line from step 1. Defaults, adjust with the user:

| Finding | Decision |
|---|---|
| Third-party CSS you must override, or `!important` count of 10 or more outside a utilities layer, or 5 or more selectors flagged `high-specificity` or `id-selector` | Adopt layers: steps 4 to 7 |
| Fewer than that, and the failing case is one or two rules | Fix locally: lower the selector's specificity, wrap resets in `:where()`, no new layers |
| A framework already declares layers | Insert yours around its names. Do not redeclare its names in another order |

A small single-file project with no conflicts gains structure cost and little else (CODERCOPS, June 2026, one practitioner source).

### Step 4: Design the order

Declare one statement, as the first line of the first stylesheet loaded:

```css
@layer reset, vendor, base, components, utilities, legacy-important;
```

| Rule | Reason |
|---|---|
| Later layers beat earlier ones whatever the specificity | Spec behaviour (MDN) |
| Unlayered CSS beats every layer for normal declarations | Your unlayered code is the final override. A layer can never beat it (csswg-drafts #6284: kept as is, "already widely used") |
| Order comes from first appearance, so the statement goes first | A later statement cannot reorder (Chrome, 2022) |
| Third-party CSS goes in `vendor`, or `thirdparty.name` when there are several | Your unlayered overrides then win without specificity tricks (OddBird, CODERCOPS). csswg-drafts #6323 gives the same advice |
| `utilities` last among normal layers | A component rule in a later layer would beat a utility |
| `legacy-important` last | `!important` order is reversed, so the last layer's important rules are the weakest of the layered ones and still beat every normal declaration |
| Name every layer | Anonymous layers cannot be extended |

A stylesheet loaded with `<link>` cannot be assigned a layer (Chrome, 2022, open WHATWG issue; **verify before relying on this**). Import it from CSS with `layer()`, or wrap it at build time. If the first CSS is a `<link>`, put the order statement in an inline `<style>` before it.

### Step 5: Migrate in batches

In this order, one batch per bullet, checker and step 2 after each:

1. Add the order statement.
2. Wrap vendor CSS: `@import url(vendor.css) layer(vendor);` after the statement.
3. Move the reset into `reset`.
4. Leave everything else unlayered. It already wins over layers, so nothing changes yet.
5. Move one stylesheet at a time into `base`, `components` or `utilities`. After each, rank the previously failing elements and compare with the browser.
6. Stop when the failing cases pass. Moving more is optional.

A migration can expose rules that only won by source order (CODERCOPS, June 2026). Re-rank the five most specific selectors in each moved file.

### Step 6: Handle `!important`

| Case | Action |
|---|---|
| Used only to beat a selector that is now in an earlier layer | Delete it |
| Used on a utility class meant to always win | Delete it if `utilities` is the last layer. Layers now do that job |
| Vendor or legacy rule you cannot yet remove | Move it to `legacy-important` so all legacy importants keep their relative specificity order |
| Needed to beat inline styles | Leave it and record the reason. An `!important` rule beats a normal style attribute |

Moving an `!important` rule into a low layer makes it stronger, not weaker, against important rules in later layers (OddBird, 2021 explainer).

### Step 7: Verify

1. Run the checker again. Errors must be 0, `no-order-statement`, `order-statement-late` and `import-no-layer` must be gone, and each remaining warning is fixed or listed with a reason.
2. For every failing case from step 2, run the resolver and `getComputedStyle`. They must show the expected value and agree with each other.
3. Open the two most visited pages and compare before and after screenshots, using fake data.
4. Run the existing test suite if there is one.
5. List what you could not test: browsers older than your target, pages behind a login.

### Step 8: Report

Use the report template. A short list of file, line, issue and fix.

## Worked example

An invented site: `vendor.css` (a button library) is imported without a layer, `app.css` declares `components` and `utilities` blocks, puts the `@layer base, components, utilities;` statement after them, sets two `!important` declarations inside layers, and uses an id selector. The page has `<a class="btn bg-brand">`, and the orange `bg-brand` background never shows.

Step 1 (9 warnings and 0 errors, six shown):

```
app.css:1  warn  import-no-layer  @import url("vendor.css") is unlayered: it beats every layered rule. Use layer(name)
app.css:2  warn  no-order-statement  layer blocks appear with no earlier `@layer a, b, c;` statement ...
app.css:5  warn  order-statement-late  @layer components was already ordered at app.css:2; this statement cannot move it
app.css:4  warn  important-in-layer  background: !important in layer components: important order is reversed ...
app.css:5  warn  utilities-not-last  layer 'utilities' is not last in the order ['components', 'base', 'utilities', 'reset']
app.css:11  warn  id-selector  #hero .btn.primary.large has an id selector, specificity (1, 3, 0)
```

Step 2:

```
WINS  vendor.css:1  .btn  layer=unlayered  spec=(0, 1, 0)  important=False  background: gray
      app.css:8  .bg-brand  layer=utilities  spec=(0, 1, 0)  important=False  background: orangered
```

The browser agrees: `getComputedStyle` gives `rgb(128, 128, 128)`. Step 3: a third-party stylesheet that must be overridden, so adopt layers.

Fixes in one batch (`app.css` only): the order statement first, the import with `layer(vendor)`, the reset in `reset`, `!important` removed from the utility and the legacy rule moved out of the way, the id selector replaced by a class. Step 7: checker `errors 0, warnings 0`, and `getComputedStyle` gives `rgb(255, 69, 0)`. The resolver named the same winner as Chromium 153 in all 18 cases of the skill's own test set, measured on 25 October 2026.

## Templates

### Matched rules in the browser (step 2)

```js
(sel) => { const el = document.querySelector(sel), out = [];
  const walk = (rules, layer) => { for (const r of rules) {
    if (r.constructor.name === 'CSSLayerBlockRule') walk(r.cssRules, layer ? layer + '.' + r.name : r.name);
    else if (r.constructor.name === 'CSSImportRule') { try { walk(r.styleSheet.cssRules, r.layerName === null ? layer : (layer ? layer + '.' : '') + r.layerName); } catch (e) {} }
    else if (r.cssRules && r.selectorText === undefined) walk(r.cssRules, layer);
    else if (r.selectorText) { let m = false; try { m = el.matches(r.selectorText); } catch (e) {} if (m) out.push((layer || 'unlayered') + ' | ' + r.selectorText + ' | ' + r.style.cssText.slice(0, 70)); } } };
  for (const s of document.styleSheets) { try { walk(s.cssRules, ''); } catch (e) {} }
  return out; }
```

Call it with a selector for the element, for example `('a.btn')`. It prints CSS only, never page text.

### Computed value (steps 2 and 7)

```js
getComputedStyle(document.querySelector('a.btn')).getPropertyValue('background-color')
```

### Report

```
Cascade report: <page>, <date>
Failing case: <element, property, expected, got>. Cause: <layer, specificity, importance of the winner>.
Checker before: errors N, warnings N. After: errors N, warnings N.
Layer order: <statement>.
Changes: <file:line  issue  fix>   (one line each)
Left as is: <warning  reason>
Not tested: <items>
```

### Starting prompt

```
Use the cascade-layers skill. Stylesheets in load order: <list>. Failing case: <element, property, expected, got>.
Third-party files I cannot edit: <list>. Framework: <name and version>. Edit on a branch in batches of one file or 20 rules and show each diff first.
```

## Failure modes and anti-patterns

| Mistake | How to spot it | Do instead |
|---|---|---|
| Expecting a later layer to beat unlayered CSS | A utility in `utilities` does nothing, and the resolver shows an unlayered rule winning | Put the vendor or legacy CSS in a layer, or move the unlayered rule into a layer |
| Treating `!important` as "wins everywhere" inside layers | An important utility is ignored by an important rule in an earlier layer | Step 6: delete it, or use `legacy-important` last |
| Letting first appearance set the order | `no-order-statement` or `order-statement-late` | One statement, first line of the first stylesheet |
| Layering a vendor sheet by editing it, or via `<link>` | A diff inside `node_modules`, or a `<link>` the browser leaves unlayered | `@import ... layer()` from your own CSS |
| Treating layers as scoping or as z-index numbers | A rule in `components` styles elements everywhere, and someone adds layers to fix one conflict | Scope selectors as before. Add a layer only past the step 3 thresholds |

## Quality bar

Run every item before handing over.

1. Checker errors are 0, and `no-order-statement`, `order-statement-late` and `import-no-layer` no longer appear.
2. Each failing case from step 2 shows the expected value in `getComputedStyle`, and the resolver names the same winner.
3. The order statement is one line, first in the first stylesheet loaded, and every layer is named.
4. No `!important` was added, and each remaining one is listed with a reason.
5. No file under `node_modules` or a vendor folder was edited.
6. Every changed line appears in the report with file, line, issue and fix.
7. The report lists what was not tested.
8. No `url(...)` value with a query string appears in the output.

## Cost

Figures are estimates from this skill's own count (words × 1.35 for prose, characters ÷ 3.5 for code and tables), not from a model tokeniser. Rival figures are estimates from the excerpts I read.

| Item | Tokens | Status | Read condition |
|---|---|---|---|
| A. Description (always loaded) | 112 | Estimate | Every session with the skill installed |
| B. `SKILL.md` body | 4,634 | Estimate | When the skill fires |
| C. `references/appendix.md` | 1,201 | Estimate | Only to check a claim or a measurement |
| C. `references/migration.md` | 510 | Estimate | Only when writing the layer statement, imports or wrappers |
| Script output: `cascade_check.py` (worked example) plus measuring snippets | 680 | Estimate | Every run. Checker output is capped with `head -40`, about 1,382 tokens |
| D. Typical run (A + B + script and snippet output) | 5,426 | Estimate | Common path: fix flagged lines, no references |
| D. Worst case (A + B + both references + 40 lines of output) | 8,439 | Estimate | Rebuilding a layout, many findings |
| E. Cheaper path | 5,201 | Estimate | Run the checker, fix only flagged lines, skip the references and the browser snippet |

Rival sizes, estimated from the excerpts I read (SKILL.md only, files they link were not sized): thedaviddias cascade-layers (tessl) about 300 words, roughly 405 tokens, no script or verification step, tessl quality score 61%; jamiojala skillforge cascade-layers-master (openskillindex) about 450 words, roughly 610 tokens, names hooks it never defines; openaec frontend-syntax-css-cascade-layers-scope (skills.sh) at least 110 visible words, full size unknown, covers `@scope` gating. This skill costs more to load than all three. What the checker saves is the agent reading and reasoning over every rule in a large stylesheet. Measured on `bootstrap.min.css` (235,601 characters) the checker printed 1,862 characters in 0.57 seconds. On the two-file worked example its output is larger than the files, so on a stylesheet under about 100 lines read the file and use the resolver.

## Handoffs

`flexbox-grid-layout.md` covers overflow and shrinking. `container-queries.md` covers components that respond to their container.

## Appendix

Tool facts, measurements and sources are in `references/appendix.md`. Migration code is in `references/migration.md`.

## Sources

Last verified 25 October 2026 for every source, except where a date says otherwise.

- Anthropic, Skill authoring best practices: https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices
- MDN, @layer: https://developer.mozilla.org/en-US/docs/Web/CSS/@layer
- Chrome for Developers, Cascade layers are coming to your browser (1 February 2022): https://developer.chrome.com/blog/cascade-layers
- OddBird, Cascade Layers explainer (2021): https://css.oddbird.net/layers/explainer/
- csswg-drafts issue 6284: https://github.com/w3c/csswg-drafts/issues/6284
- csswg-drafts issue 6323 (archived July 2024): https://lists.w3.org/Archives/Public/public-css-archive/2024Jul/0063.html
- CODERCOPS, CSS Cascade Layers: Production Notes (8 June 2026): https://blog.codercops.com/blog/css-cascade-layers-production-2026/
- thedaviddias, cascade-layers (via tessl). Rival implementation: https://tessl.io/registry/thedaviddias
- jamiojala, cascade-layers-master (via openskillindex). Rival implementation: https://openskillindex.com
- openaec, frontend-syntax-css-cascade-layers-scope (via skills.sh). Rival implementation: https://skills.sh

---
Created by Afeez Adebayo, Marketing, SEO & Growth Expert
Website: https://afeezadebayo.com
LinkedIn: https://www.linkedin.com/in/afeez-adebayo/
X: https://x.com/za_icon
Source: https://afeezadebayo.com/skills-md/frontend-development/css-layout/cascade-layers/

Licence: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). You may copy, share and adapt this skill, including for commercial use, as long as you credit Afeez Adebayo and link to the source URL above. Keep this notice in any copy or adapted version.

Workflow

Step 0: Confirm scope

List the files in load order. Minified input is fine for the checker, but fixes go in source files. One failing case at a time.

Step 1: Run the checker

python3 scripts/cascade_check.py vendor.css app.css | head -40

Read only its output. Do not read the script. Pass files in load order. --max-spec 3 changes the class-level threshold. Exit code 1 means an error. The last two lines give the layer order and counts. Each code has one fix, in this table.

CodeLevelMeaningFix
import-no-layerwarnAn @import without layer() is unlayered, so it beats every layered rule@import url(x.css) layer(vendor);
import-after-ruleserror@import after a style rule is invalid and ignoredMove it above the rules, after any @layer statement
no-order-statementwarnLayer blocks appear with no earlier @layer a, b;, so order follows first appearanceAdd one statement at the top of the first stylesheet loaded
order-statement-latewarnA statement names layers already ordered; it cannot move themDelete it and put the full order in the first statement
utilities-not-lastwarnA later layer beats utilitiesMove utilities to the end of the order, before any *important* layer
important-in-layerwarnImportant order is reversed: an earlier layer's !important beats a later oneStep 6
anonymous-layerinfoAn unnamed layer cannot be extended or reordered by nameName it
id-selectorwarnAn id selector, specificity (1,0,0) or moreClass selector
high-specificitywarnClass-level specificity of 3 or moreFlatten the selector, or place it in a later layer

Step 2: Find what wins for the failing case

  1. In the browser, run the matched-rules snippet (Templates) on the element. It lists each matching rule with its layer. Cross-origin stylesheets are skipped by the browser, so read those in DevTools.
  2. Run the resolver with the selectors from that list:
python3 scripts/cascade_check.py vendor.css app.css --rank background --sel ".btn" --sel ".bg-brand"

The first line starts WINS and shows layer, specificity and importance. It models layers, importance, specificity and source order. It does not model style attributes, animations or selectors outside the files you passed.

  1. Confirm with getComputedStyle(el).background in the browser. If the resolver and the browser disagree, a style attribute, an unscanned file or a transition is involved. Stop and find it before editing.

Step 3: Decide whether to adopt layers

Read the counts line from step 1. Defaults, adjust with the user:

FindingDecision
Third-party CSS you must override, or !important count of 10 or more outside a utilities layer, or 5 or more selectors flagged high-specificity or id-selectorAdopt layers: steps 4 to 7
Fewer than that, and the failing case is one or two rulesFix locally: lower the selector's specificity, wrap resets in :where(), no new layers
A framework already declares layersInsert yours around its names. Do not redeclare its names in another order

A small single-file project with no conflicts gains structure cost and little else (CODERCOPS, June 2026, one practitioner source).

Step 4: Design the order

Declare one statement, as the first line of the first stylesheet loaded:

@layer reset, vendor, base, components, utilities, legacy-important;
RuleReason
Later layers beat earlier ones whatever the specificitySpec behaviour (MDN)
Unlayered CSS beats every layer for normal declarationsYour unlayered code is the final override. A layer can never beat it (csswg-drafts #6284: kept as is, "already widely used")
Order comes from first appearance, so the statement goes firstA later statement cannot reorder (Chrome, 2022)
Third-party CSS goes in vendor, or thirdparty.name when there are severalYour unlayered overrides then win without specificity tricks (OddBird, CODERCOPS). csswg-drafts #6323 gives the same advice
utilities last among normal layersA component rule in a later layer would beat a utility
legacy-important last!important order is reversed, so the last layer's important rules are the weakest of the layered ones and still beat every normal declaration
Name every layerAnonymous layers cannot be extended

A stylesheet loaded with <link> cannot be assigned a layer (Chrome, 2022, open WHATWG issue; verify before relying on this). Import it from CSS with layer(), or wrap it at build time. If the first CSS is a <link>, put the order statement in an inline <style> before it.

Step 5: Migrate in batches

In this order, one batch per bullet, checker and step 2 after each:

  1. Add the order statement.
  2. Wrap vendor CSS: @import url(vendor.css) layer(vendor); after the statement.
  3. Move the reset into reset.
  4. Leave everything else unlayered. It already wins over layers, so nothing changes yet.
  5. Move one stylesheet at a time into base, components or utilities. After each, rank the previously failing elements and compare with the browser.
  6. Stop when the failing cases pass. Moving more is optional.

A migration can expose rules that only won by source order (CODERCOPS, June 2026). Re-rank the five most specific selectors in each moved file.

Step 6: Handle !important

CaseAction
Used only to beat a selector that is now in an earlier layerDelete it
Used on a utility class meant to always winDelete it if utilities is the last layer. Layers now do that job
Vendor or legacy rule you cannot yet removeMove it to legacy-important so all legacy importants keep their relative specificity order
Needed to beat inline stylesLeave it and record the reason. An !important rule beats a normal style attribute

Moving an !important rule into a low layer makes it stronger, not weaker, against important rules in later layers (OddBird, 2021 explainer).

Step 7: Verify

  1. Run the checker again. Errors must be 0, no-order-statement, order-statement-late and import-no-layer must be gone, and each remaining warning is fixed or listed with a reason.
  2. For every failing case from step 2, run the resolver and getComputedStyle. They must show the expected value and agree with each other.
  3. Open the two most visited pages and compare before and after screenshots, using fake data.
  4. Run the existing test suite if there is one.
  5. List what you could not test: browsers older than your target, pages behind a login.

Step 8: Report

Use the report template. A short list of file, line, issue and fix.

Worked example

An invented site: vendor.css (a button library) is imported without a layer, app.css declares components and utilities blocks, puts the @layer base, components, utilities; statement after them, sets two !important declarations inside layers, and uses an id selector. The page has <a class="btn bg-brand">, and the orange bg-brand background never shows.

Step 1 (9 warnings and 0 errors, six shown):

app.css:1  warn  import-no-layer  @import url("vendor.css") is unlayered: it beats every layered rule. Use layer(name)
app.css:2  warn  no-order-statement  layer blocks appear with no earlier `@layer a, b, c;` statement ...
app.css:5  warn  order-statement-late  @layer components was already ordered at app.css:2; this statement cannot move it
app.css:4  warn  important-in-layer  background: !important in layer components: important order is reversed ...
app.css:5  warn  utilities-not-last  layer 'utilities' is not last in the order ['components', 'base', 'utilities', 'reset']
app.css:11  warn  id-selector  #hero .btn.primary.large has an id selector, specificity (1, 3, 0)

Step 2:

WINS  vendor.css:1  .btn  layer=unlayered  spec=(0, 1, 0)  important=False  background: gray
      app.css:8  .bg-brand  layer=utilities  spec=(0, 1, 0)  important=False  background: orangered

The browser agrees: getComputedStyle gives rgb(128, 128, 128). Step 3: a third-party stylesheet that must be overridden, so adopt layers.

Fixes in one batch (app.css only): the order statement first, the import with layer(vendor), the reset in reset, !important removed from the utility and the legacy rule moved out of the way, the id selector replaced by a class. Step 7: checker errors 0, warnings 0, and getComputedStyle gives rgb(255, 69, 0). The resolver named the same winner as Chromium 153 in all 18 cases of the skill's own test set, measured on 25 October 2026.

Templates

Matched rules in the browser (step 2)

(sel) => { const el = document.querySelector(sel), out = [];
  const walk = (rules, layer) => { for (const r of rules) {
    if (r.constructor.name === 'CSSLayerBlockRule') walk(r.cssRules, layer ? layer + '.' + r.name : r.name);
    else if (r.constructor.name === 'CSSImportRule') { try { walk(r.styleSheet.cssRules, r.layerName === null ? layer : (layer ? layer + '.' : '') + r.layerName); } catch (e) {} }
    else if (r.cssRules && r.selectorText === undefined) walk(r.cssRules, layer);
    else if (r.selectorText) { let m = false; try { m = el.matches(r.selectorText); } catch (e) {} if (m) out.push((layer || 'unlayered') + ' | ' + r.selectorText + ' | ' + r.style.cssText.slice(0, 70)); } } };
  for (const s of document.styleSheets) { try { walk(s.cssRules, ''); } catch (e) {} }
  return out; }

Call it with a selector for the element, for example ('a.btn'). It prints CSS only, never page text.

Computed value (steps 2 and 7)

getComputedStyle(document.querySelector('a.btn')).getPropertyValue('background-color')

Report

Cascade report: <page>, <date>
Failing case: <element, property, expected, got>. Cause: <layer, specificity, importance of the winner>.
Checker before: errors N, warnings N. After: errors N, warnings N.
Layer order: <statement>.
Changes: <file:line  issue  fix>   (one line each)
Left as is: <warning  reason>
Not tested: <items>

Starting prompt

Use the cascade-layers skill. Stylesheets in load order: <list>. Failing case: <element, property, expected, got>.
Third-party files I cannot edit: <list>. Framework: <name and version>. Edit on a branch in batches of one file or 20 rules and show each diff first.

Failure modes and anti-patterns

MistakeHow to spot itDo instead
Expecting a later layer to beat unlayered CSSA utility in utilities does nothing, and the resolver shows an unlayered rule winningPut the vendor or legacy CSS in a layer, or move the unlayered rule into a layer
Treating !important as "wins everywhere" inside layersAn important utility is ignored by an important rule in an earlier layerStep 6: delete it, or use legacy-important last
Letting first appearance set the orderno-order-statement or order-statement-lateOne statement, first line of the first stylesheet
Layering a vendor sheet by editing it, or via <link>A diff inside node_modules, or a <link> the browser leaves unlayered@import ... layer() from your own CSS
Treating layers as scoping or as z-index numbersA rule in components styles elements everywhere, and someone adds layers to fix one conflictScope selectors as before. Add a layer only past the step 3 thresholds

Quality bar

Run every item before handing over.

  1. Checker errors are 0, and no-order-statement, order-statement-late and import-no-layer no longer appear.
  2. Each failing case from step 2 shows the expected value in getComputedStyle, and the resolver names the same winner.
  3. The order statement is one line, first in the first stylesheet loaded, and every layer is named.
  4. No !important was added, and each remaining one is listed with a reason.
  5. No file under node_modules or a vendor folder was edited.
  6. Every changed line appears in the report with file, line, issue and fix.
  7. The report lists what was not tested.
  8. No url(...) value with a query string appears in the output.

Cost

Figures are estimates from this skill's own count (words × 1.35 for prose, characters ÷ 3.5 for code and tables), not from a model tokeniser. Rival figures are estimates from the excerpts I read.

ItemTokensStatusRead condition
A. Description (always loaded)112EstimateEvery session with the skill installed
B. SKILL.md body4,634EstimateWhen the skill fires
C. references/appendix.md1,201EstimateOnly to check a claim or a measurement
C. references/migration.md510EstimateOnly when writing the layer statement, imports or wrappers
Script output: cascade_check.py (worked example) plus measuring snippets680EstimateEvery run. Checker output is capped with head -40, about 1,382 tokens
D. Typical run (A + B + script and snippet output)5,426EstimateCommon path: fix flagged lines, no references
D. Worst case (A + B + both references + 40 lines of output)8,439EstimateRebuilding a layout, many findings
E. Cheaper path5,201EstimateRun the checker, fix only flagged lines, skip the references and the browser snippet

Rival sizes, estimated from the excerpts I read (SKILL.md only, files they link were not sized): thedaviddias cascade-layers (tessl) about 300 words, roughly 405 tokens, no script or verification step, tessl quality score 61%; jamiojala skillforge cascade-layers-master (openskillindex) about 450 words, roughly 610 tokens, names hooks it never defines; openaec frontend-syntax-css-cascade-layers-scope (skills.sh) at least 110 visible words, full size unknown, covers @scope gating. This skill costs more to load than all three. What the checker saves is the agent reading and reasoning over every rule in a large stylesheet. Measured on bootstrap.min.css (235,601 characters) the checker printed 1,862 characters in 0.57 seconds. On the two-file worked example its output is larger than the files, so on a stylesheet under about 100 lines read the file and use the resolver.

Handoffs

flexbox-grid-layout.md covers overflow and shrinking. container-queries.md covers components that respond to their container.

Appendix

Tool facts, measurements and sources are in references/appendix.md. Migration code is in references/migration.md.

Sources

Last verified 25 October 2026 for every source, except where a date says otherwise.

What this skill does

CSS conflicts waste hours because the rule that wins is rarely the one you expect. A vendor stylesheet loaded without a layer beats every layered rule. A utility class in the last layer does nothing against an unlayered rule. An !important inside a layer behaves backwards. This skill gives the agent a fixed order for sorting that out.

First a read-only checker reads the stylesheets in load order and reports nine kinds of problem. Then a resolver ranks every declaration for the failing property and names the winner, and the agent confirms it with getComputedStyle in the browser. Only then does it decide whether layers are worth adopting, using stated thresholds, and migrate one small batch at a time with your approval of each diff.

Who it is for

Use it if you override third-party CSS, maintain a stylesheet where !important keeps spreading, or want to introduce @layer into an existing codebase without breaking pages. It suits developers who use Claude Code, Cursor or similar agents and want the agent to prove which rule wins before it edits anything.

It is not for layout overflow, which the Flexbox and Grid Layout skill covers, or for CSS-in-JS class order at runtime, because it reads static CSS only. It leaves @scope, CSS Modules and naming schemes out of this release.

What is inside

The file is 3,146 words (version 1.0.0) with one script and two reference files. It holds:

  • Seven guardrails, including never editing vendor files, no url() values in reports and a rule that nothing is claimed without a browser check.
  • Seven inputs the agent must have first, with the exact question for each.
  • A workflow of steps 0 to 8, a table that gives one fix per checker code and a table of thresholds for when to adopt layers.
  • One standard-library script, scripts/cascade_check.py, with nine problem codes and a resolver. It read a 235,601-character minified Bootstrap file in 0.57 seconds.
  • A worked example with real output (0 errors and 9 warnings before, 0 and 0 after), measured in Chromium 153. The resolver named the same winner as the browser in all 18 cases of its test set.
  • A matched-rules snippet, a computed-value snippet, a report template and a starting prompt, five failure modes and an eight-item quality bar.
  • Two reference files: migration code, and an appendix with sources and measurements.

The token costs below are estimates from the skill’s own count: words times 1.35 for prose and characters divided by 3.5 for code and tables. They are not from a model tokeniser.

ItemTokensStatus
Description (always loaded)112Estimate
SKILL.md body (when the skill fires)4,634Estimate
references/appendix.md, read only to check a claim1,201Estimate
references/migration.md, read only when writing the layer statement or imports510Estimate
Typical run (description, body, checker and snippet output)5,426Estimate
Cheaper path (checker, fix flagged lines, no references or browser snippet)5,201Estimate
Worst case (both references, 40 lines of checker output)8,439Estimate

This skill costs more to load than the rivals I could size, roughly 405 and 610 tokens for SKILL.md alone, and the third was not sized. The checker pays off on large files: on Bootstrap it printed 1,862 characters against 235,601 to read. On a stylesheet under about 100 lines the output is larger than the file, and the skill says to read the file and use the resolver instead.

How to install and use it in Claude, Cursor and other agents

Claude. Keep the folder as cascade-layers/ with SKILL.md, scripts/ and references/ inside, and zip it. In Claude, open Customize, then Skills, then Upload a skill. In Claude Code, put the folder in ~/.claude/skills/ or .claude/skills/ in a project.

Cursor and other agents. Put the folder where your tool reads skills, or paste the body into its rules and keep the script beside your code. Then send the starting prompt from the Templates section.

Before you install. Read the script first. It reads CSS files and writes nothing. The skill tells the agent to edit only on a branch, in batches you approve, and never to touch vendor files. Install skills only from sources you trust.

A worked example

An invented site imports a button library without a layer, declares its layers in app.css before the order statement, sets two !important declarations inside layers and uses an id selector. The orange bg-brand background on <a class="btn bg-brand"> never shows. The checker reports 9 warnings and no errors.

The resolver puts the unlayered vendor .btn first, and getComputedStyle agrees with rgb(128, 128, 128). Because a third-party stylesheet must be overridden, the agent adopts layers. One batch to app.css puts the order statement first, imports the vendor file with layer(vendor), removes the !important from the utility, moves the legacy rule aside and swaps the id selector for a class. The checker then reports 0 errors and 0 warnings, and the browser returns rgb(255, 69, 0).

Common mistakes

The skill is built around five ways this goes wrong:

  • Expecting a later layer to beat unlayered CSS. It never does for normal declarations. The skill puts vendor or legacy CSS in a layer instead.
  • Treating !important as wins-everywhere inside layers. Important order is reversed, so an earlier layer’s important rule beats a later one. The skill deletes the flag or uses a last legacy-important layer.
  • Letting first appearance set the order. A later @layer statement cannot move a layer. The skill writes one statement as the first line of the first stylesheet.
  • Layering a vendor file by editing it. The skill wraps it at the @import with layer() and never edits node_modules.
  • Treating layers as scoping or as a cure for a small project. It adopts them only past stated thresholds.

Flexbox and Grid Layout covers overflow, shrinking and grid tracks, and Container Queries covers components that respond to their own container. Browse the CSS and Layout set, the full Frontend Development category or the Skills-md library.

Frequently asked questions

Specificity only decides between rules in the same layer, or between rules that are both unlayered. A rule in a later layer beats one in an earlier layer whatever the specificity, and an unlayered rule beats every layered rule. The skill’s resolver prints the layer, specificity and importance of each candidate so you can see which of these applied.

For normal declarations, unlayered styles override all layers, even ones declared later. MDN documents this, and the CSS working group kept it after discussion because it is widely used. If a vendor file is unlayered, import it with layer(vendor) so your utilities can win. In the skill’s example that change turned gray into orangered.

Yes. For important declarations the order reverses: an important rule in an earlier layer beats an important rule in a later one, and a layered important rule beats an unlayered important rule. The checker flags each !important inside a layer as important-in-layer, and step 6 says whether to delete it or move it into a last legacy-important layer.

Import it from your own CSS: @import url(vendor.css) layer(vendor); after the layer order statement. The import must come before other rules, or the browser ignores it, which I measured in Chromium 153. Never edit the vendor file. A stylesheet loaded with a link tag could not be layered when the feature shipped, so verify that for your browsers.

MDN lists @layer as Baseline widely available, in browsers since March 2022. Chrome’s launch post named Chromium 99, Firefox 97 and Safari 15.4 as the first versions. The skill still asks which browsers you must support before it plans a migration.

Usually not. The skill adopts layers only when you must override third-party CSS, when ten or more !important declarations sit outside a utilities layer, or when five or more selectors are flagged as id or high specificity. Below that it fixes the one or two failing rules locally. These thresholds are defaults you can change.

The description is about 112 tokens and loads in every session. The SKILL.md body is about 4,600 tokens when the skill fires. A typical run is about 5,400 tokens with checker output, the cheapest path is about 5,200 and the worst case is about 8,400. All figures are estimates, not from a model tokeniser.

Changelog

  • 1.0.0, 25 October 2026: first release.

Use, adapt and share this skill under CC BY 4.0: credit Afeez Adebayo and link to this page.