From frontend
Guides conversion of flat CSS to nested & syntax while ensuring stylelint compliance with no-descending-specificity and no-duplicate-selectors rules. Use for CSS refactoring or fixing specificity/duplicate linter errors.
How this skill is triggered — by the user, by Claude, or both
Slash command
/frontend:css-nestingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Flat-to-nested CSS conversion breaks when done incrementally because each edit shifts specificity ordering, triggering cascading linter errors. This skill prevents that by requiring upfront analysis before any edits.
Flat-to-nested CSS conversion breaks when done incrementally because each edit shifts specificity ordering, triggering cascading linter errors. This skill prevents that by requiring upfront analysis before any edits.
& syntaxno-descending-specificity or no-duplicate-selectors errors after nestingno-descending-specificitySelectors matching the same DOM element must appear in ascending specificity order in source. A .foo (0,1,0) appearing after .bar:hover .foo (0,3,0) is an error because both can match a .foo element.
Selectors matching different elements don't conflict regardless of order.
no-duplicate-selectorsTwo rule blocks resolving to the same selector are forbidden. Nesting changes resolved selectors: & .child inside .parent resolves to .parent .child. A second .parent block is a duplicate even if the contents differ.
Exception: the same selector inside vs outside a @media block is allowed (different parent nodes).
Write out the parent-child nesting from the HTML template:
.container
.wrapper
.item
.label
For each selector in the flat CSS, calculate specificity and note which DOM element it matches:
.wrapper (0,1,0) matches .wrapper
.wrapper:hover (0,2,0) matches .wrapper
.item (0,1,0) matches .item
.wrapper:hover .item (0,3,0) matches .item
.label (0,1,0) matches .label
.container:has(:hover) .wrapper:not(:has(:hover)) (0,4,0) matches .wrapper
For each element, the selectors that match it must appear in ascending specificity order in the final source:
Selectors matching .wrapper:
.wrapper (0,1,0) ← first
.wrapper:hover (0,2,0)
.container:has(:hover) .wrapper:not(:has(:hover)) (0,4,0) ← last
Selectors matching .item:
.item (0,1,0) ← first
.wrapper:hover .item (0,3,0) ← last
Place blocks so that within each element group, specificity ascends in source order. Key principles:
Nest children inside parents to match DOM hierarchy. & .item inside .wrapper resolves to .wrapper .item (0,2,0) — this increases specificity vs flat .item (0,1,0), which is fine as long as the ordering still ascends.
High-specificity cross-cutting selectors go last. If .container:has(:hover) .wrapper:not(...) at (0,4,0) matches .wrapper, it must appear in source after all other .wrapper-matching selectors. Since it's nested under .container, put the .container block after the .wrapper block.
One block per selector. Can't have two .container blocks — use one block with all its rules.
@media nests inside its parent block. @media inside .wrapper { } avoids both duplicate-selector issues and keeps related rules together.
<style> block in one editNever do incremental edits. Write the complete nested CSS as a single replacement. Each incremental edit shifts source order and triggers new linter errors.
| Component | Specificity added |
|---|---|
Element type (div, span) | (0,0,1) |
Class, attribute, pseudo-class (.foo, :hover, :nth-child()) | (0,1,0) |
ID (#bar) | (1,0,0) |
:is(), :not(), :has() | Highest specificity of their argument |
& (nesting selector) | Same as :is() — takes parent's specificity |
~, +, >, (combinators) | Add nothing |
Nesting with & uses :is() wrapping semantics per CSS Nesting Module Level 1. The & selector adopts the highest specificity from the parent selector list.
This is the pattern that breaks naive nesting — a high-specificity desaturate rule that crosses parent-child boundaries:
Problem: .container:has(:hover) .child:not(:has(:hover)) at (0,4,0) matches .child elements. If .container block comes first with this rule inside, then .child block comes second at (0,1,0), that's descending specificity.
Solution: Put .child block first (all its selectors ascending), then .container block second (with the desaturate rule at the end). They target different elements so the block ordering doesn't trigger no-descending-specificity.
/* .child block first — all .child-matching selectors in ascending order */
.child {
/* (0,1,0) matches .child */
& .grandchild {
/* (0,2,0) matches .grandchild */
}
&:hover {
/* (0,2,0) matches .child */
& .grandchild {
/* (0,3,0) matches .grandchild — ascending */
}
}
&:nth-child(2n) {
/* (0,2,0) matches .child — same specificity as :hover, OK */
}
@media (width > 768px) {
/* desktop overrides — same selectors allowed inside @media */
&:nth-child(2n) {
/* different parent node, not a duplicate */
}
}
}
/* .container block second — highest-specificity .child selector comes last */
.container {
/* (0,1,0) matches .container — different element, no conflict */
@media (width > 768px) {
/* desktop overrides */
}
/* Desaturate — (0,4,0) matches .child — after all .child selectors above */
&:has(:hover) .child:not(:has(:hover)) {
opacity: 0.85;
& .grandchild {
/* (0,5,0) matches .grandchild — after all .grandchild selectors above */
filter: grayscale(100%);
}
}
}
npx claudepluginhub fubits1/svelte-skills --plugin frontendProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.