---
name: modern-css
description: Use when touching any CSS — new files, edits, audits, refactors, `<style>` blocks, CSS-in-JS, Tailwind config, design-token files, or reviewing CSS a teammate wrote. Applies to vanilla CSS, shadow DOM internals, web-component consumers, VS Code webviews, and framework-scoped styles. Skip only for files with no styles at all (e.g. `.py`/`.go`/`.rs` without embedded CSS).
---

# Modern CSS

> **STOP.** Before any CSS edit, audit, or review, output the detection one-liner to the user:
>
> ```
> Target: {browser target}. Tokens: {explicit/implicit/none, with prefix if any}. Boundary: {shadow internals / light DOM / web-component consumer}.
> ```
>
> **This is non-negotiable.** It gives the user an interrupt point — they can correct miscalibration before the first edit corrupts output. Skipping the one-liner is the most common way this skill fails. Violating the letter of this rule violates the spirit of this skill; there are no exceptions for "simple edits," "quick fixes," or "I already know this project."
>
> **How to fill it in:** run the detection spine below (Step 1 browser target → Step 2 tokens → Step 3 boundary).
> **What to do next:** before the first CSS edit, (a) create one TodoWrite task per applicable reference (see routing table) and (b) `Read` each file to mark its task complete. Reading means the Read tool, not memory. A reference with no Todo is a reference that will not get read — this is how prior sessions of this skill have failed.

Write CSS that matches the project's real context — not generic 2018 CSS, not bleeding-edge features the target doesn't support, not raw values when the project has tokens.

## How to use this skill

1. Run the detection spine (below) once per styling context.
2. Report what was detected — the one-liner is mandatory.
3. For each situation in the routing table that applies, create a TodoWrite task "Read modern-css references/{leaf}.md — {one-line why}" and complete it by Read-ing the file. Do this before the first CSS edit. See "Enforcing reference loading" below.
4. Follow the load-bearing rules at all times.

## Detection spine

Run once per styling context (new file, new component, or context shift). Cache mentally for the session.

### Step 1 — Browser target

Check signals in order. First match wins.

1. **`package.json` has `engines.vscode`** → VS Code extension path:
   - **Has web entry point** (`browser` field in `package.json`) → target is **Baseline "widely available"** (web surface = many browsers/versions).
   - **Desktop-only** → map the VS Code engine version to its Electron version, then to its Chromium version. Use that Chromium as the target.
2. **`.browserslistrc`, `browserslist`, or `package.json.browserslist`** → use the query literally.
3. **Explicit note in README / CLAUDE.md** about browser support → honor it.
4. **None of the above** → default to **Baseline "widely available"**.

**Output:** a concrete target string, e.g. "Chromium 122+", "Baseline widely available", "last 2 Chrome + last 2 Firefox".

### Step 2 — Token system

Detect explicit first. If nothing, infer implicit. Never propose formal tokenization unsolicited.

**Explicit signals:**

- `--vscode-*` tokens referenced anywhere → VS Code webview context. `--vscode-*` IS the semantic layer. Use it directly; don't wrap it.
- CSS files with `--*:` declarations → enumerate via grep.
- Token files: `tokens.css`, `theme.css`, `variables.css`, or a `design-tokens/` directory.
- Tailwind config (`tailwind.config.{js,ts,cjs,mjs}`) → extract theme tokens.
- CSS-in-JS theme exports.

**Convention inspection (when explicit tokens exist):**

- Casing (kebab / camel / other)
- Prefix pattern (e.g., `--gl-*`)
- Tier structure (primitives only / primitive + semantic / three-tier)

**Implicit pattern fallback (no explicit tokens):**

- Grep existing CSS for actual values used: spacing (padding, margin, gap), sizing, typography (font-size, line-height), border-radius.
- Extract values that appear 3+ times → identify implicit scales (e.g., "4/8/12/16/24/32px — 8-point spacing").
- Match these when writing new CSS.

**Output shape:**

- _Explicit:_ "Project tokens: `--gl-*` prefix, kebab-case, two-tier. Use `--gl-space-md`, not `16px`."
- _Implicit:_ "No explicit tokens. Implicit spacing: 4/8/12/16/24/32 (8-point). Match these."
- _Neither:_ "No consistent patterns. Use reasonable round values."
- _VS Code:_ always flag `--vscode-*` availability.

### Step 3 — Styling boundary

**Signals:**

- `customElements.define` / `extends HTMLElement` in target file → shadow root context likely.
- Lit / Stencil / FAST imports in the file.
- Deps on `@spectrum-web-components`, `shoelace`, etc.
- `<style>` tag inside a lit template → **shadow root internals**.
- Plain `.css` file or global stylesheet → **light DOM**.
- Styling an existing custom element from outside → **web component consumer**.

**Output — one label:**

- **Shadow root internals** → use `:host`, styles are scoped automatically, expose `::part` thoughtfully.
- **Web component consumer** → custom properties + `::part` + `::slotted` only; don't reach inside.
- **Light DOM** → standard cascade.

### Report what was detected

Before writing CSS, output a one-liner:

> Target: {target}. Tokens: {tokens}. Boundary: {boundary}.

This is non-negotiable. It surfaces miscalibration before it corrupts output. The user can interrupt and correct.

### Ambiguity handling

| Dimension | Ambiguous →                                                           |
| --------- | --------------------------------------------------------------------- |
| Target    | Default to Baseline "widely available"                                |
| Tokens    | If none detected, flag "no token system detected"; use literal values |
| Boundary  | Default to light DOM; ask if writing component code                   |

## Load-bearing rules (always apply)

- Never reach for `!important` or specificity escalation to fix a cascade problem. Use `@layer` or `:where()` instead. (→ `references/cascade.md`)
- Never invent CSS syntax. If unsure a property or value exists as specified, flag uncertainty rather than guessing. Verify via MDN or caniuse when tool access allows.
- Never use features above the detected browser target. If a modern feature is needed but unsupported, say so instead of silently downgrading.
- Never hardcode values when explicit or implicit tokens exist. Match the token system that's already there; don't impose a new one.
- Never use viewport media queries for component-internal layout. Use container queries instead. Pages still use media queries; preference/capability media queries (`prefers-reduced-motion`, `hover`, etc.) are always media queries regardless. (→ `references/responsive.md`)
- Never reach into shadow DOM from outside. Use `::part`, `::slotted`, or custom properties. (→ `references/selectors.md`)
- Never use `transform: translateZ(0)` or permanent `will-change` for layer promotion. Use `isolation: isolate` or `contain` instead. (→ `references/performance.md`)
- Never fix layering by escalating a raw z-index. A raw z-index climbing past ~100 is the signal to use a semantic tier token (`--gl-z-*` in GitLens webviews), `isolation: isolate` on the trapping ancestor, or the top layer (`<dialog>.showModal()` / `[popover]`) — not a bigger number. Shadow-isolated internals keep small raw ordinals (`1`/`2`/`3`). (→ `references/performance.md`)
- In GitLens webviews, never apply a `--gl-shadow-*` elevation token raw — go through the `elevated-surface` helper (Lit `elevatedSurface` / SCSS `@include elevated-surface`), which pairs the shadow with the border that survives forced-colors (where every shadow renders as nothing). Tokens, tiers, and the elevation model live in `docs/webview-styling.md`.
- Name custom properties by role, not appearance. `--color-accent`, not `--blue`. In component code, prefer semantic tokens over primitives. (→ `references/theming.md`)

## When to load which reference

Read the relevant reference file before writing CSS in these cases:

| Situation                                                                                                                                     | Read                        |
| --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| About to write grid, flex, sizing, or positioning code                                                                                        | `references/layout.md`      |
| Adapting to viewport, container size, or user preferences (reduced motion, color scheme, contrast)                                            | `references/responsive.md`  |
| Hitting a z-index puzzle, optimizing rendering, handling offscreen content, or stacking-context issues                                        | `references/performance.md` |
| Fighting specificity, tempted by `!important`, organizing cascade order                                                                       | `references/cascade.md`     |
| Writing selectors, state-based styling, pseudo-classes, or styling inside/consuming a web component                                           | `references/selectors.md`   |
| Working with colors, theming, custom properties, dark mode, or authoring tokens                                                               | `references/theming.md`     |
| Animating elements, adding transitions, using scroll-driven effects, implementing view transitions, or positioning popovers / anchor-based UI | `references/animation.md`   |

If a task spans multiple situations (e.g., building a new component touches layout + theming), load all matching references.

## Enforcing reference loading (TodoWrite gating)

Two prior field tests of this skill (n=1, n=2) both read **zero** references. Prose that says "read the reference before writing CSS" gets skipped. A pending TodoWrite task is harder to skip because it sits in the visible task list as incomplete.

**Flow:**

1. Run detection spine → output the one-liner.
2. Scan the routing table. For each row that applies to the current task, call TodoWrite to create a task formatted:
   ```
   Read modern-css references/{leaf}.md — {one-line why it applies}
   ```
3. Before the first CSS Read or Edit against project code, work through the tasks: use the Read tool on each referenced file, then mark the corresponding task completed. You may read in parallel.
4. If, after Read-ing, a reference turns out not to apply, mark the task completed with a one-sentence note in chat explaining what you expected vs. what the file covered. Never silently skip.

**Rationalization check.** If you catch yourself thinking "I already know what that reference says" — create the task and Read anyway. Skills evolve; your memory is stale. The discipline exists because the skill's authors have seen agents (including this one) confidently skip references and ship miscalibrated output. Memory is not evidence.

**What "applies" means.** An audit that touches cascade, tokens, animations, and selectors creates _four_ tasks, not one. For audits specifically, assume `cascade.md`, `theming.md`, and the audit-relevant subset of `selectors.md`/`responsive.md`/`animation.md`/`performance.md` all apply unless you can name what makes them irrelevant.

## When auditing existing CSS

When reviewing a codebase rather than writing new CSS:

- **Weight recommendations to the deployment context.** A VS Code extension that doesn't support RTL still benefits from logical properties (expressiveness), but a 290-instance migration has different priority than in an i18n-ready web app. State the context-specific rationale, not generic best-practice justification.
- **Triage within findings.** Don't treat all instances as equal severity. A continuous spinner without `prefers-reduced-motion` is higher severity than a 150ms opacity fade. A hardcoded color in a themed component is higher severity than one in a syntax-highlighting block. Report the severity distribution, not just the count.
- **Recommend implementation strategy, not just the fix.** "Create a shared mixin" vs. "wrap individually" vs. "global reset" are different approaches with different tradeoffs. Name the strategy.
- **Classify hardcoded values before recommending token migration.** Three categories: (a) should _be_ a token (repeated semantic value), (b) should _derive from_ a token via `color-mix()` or relative color syntax (opacity/lightness variant), (c) intentionally absolute (shadows, syntax highlighting, one-off decorative). Only (a) and (b) need action.
- **Check for architectural gaps, not just lint-level issues.** In web component codebases: `::part()` exposure (too many? too few?), `:host` styling consistency, style duplication across components, animation performance (compositor-friendly `transform`/`opacity` vs. layout-triggering `width`/`height`/`top`/`left`), dead/unused CSS.
- **Prioritize across findings.** Rank by: (1) _Broken under real conditions_ — hardcoded values that fail on theme switch, animations that ignore reduced-motion, z-index bugs from misunderstood stacking contexts. These are bugs. (2) _Architectural debt that compounds_ — style duplication, inconsistent `:host` patterns, no `::part` exposure strategy. Gets worse with every new component. (3) _Modernization with measurable benefit_ — performance gains, fewer selectors, simpler state. Worth doing but not urgent. (4) _Modernization for code quality_ — cleaner shorthands, modern selectors replacing working old ones. Apply to new code, migrate opportunistically. Lead with tier 1, end with tier 4.
- **Specify migration scope for large findings.** For findings with 50+ instances: recommend _new-code-only_ unless the finding is severity tier 1 (broken under real conditions). For tier 1, recommend a focused migration. For tier 2-4, recommend a lint rule for new code + opportunistic migration when already touching the file. A small team cannot absorb a 290-instance migration alongside feature work.
