plugins/style-agent/skills/css/SKILL.md
Use when the developer wants to author CSS from a plain-language description — emits a raw CSS/SCSS rule or an inline style attribute, reusing the project's own custom properties. For a utility-class string instead, use create-utilities.
npx skillsauth add shawn-sandy/acss-plugins cssInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Turn a plain-language description of visual intent into a raw CSS/SCSS rule or an inline style attribute. Values are mapped onto the project's existing CSS custom properties when they match, and modern-CSS features with known footguns (@container, @layer, fluid clamp(), state selectors) are emitted from the bundled references rather than from memory.
This skill authors CSS. The sibling skills transform it: /css-to-class collapses utility soup into a named class, /inline-style-to-class promotes an existing inline style into one, and /create-utilities emits a utility-class string for projects that use a utility framework.
| Form | Example |
|---|---|
| Plain description | "a flex row aligned centered with a small gap" |
| Description with mode | /css inline "a red 1rem-padded box" |
| Description with target | "a card with a soft shadow — put it in src/styles/main.css" |
| Element with intent | <button> <!-- primary button with a hover state --> |
Stages run in this order. Nothing is emitted until the clarification gate resolves.
AskUserQuestion call. When no trigger fires, skip this stage entirely.Runs before anything is emitted. Every triggered question goes into a single batched AskUserQuestion call — never a second interrogation round. A concrete, fully-specified description skips the gate entirely and is emitted with no questions asked.
Four triggers:
"make it look nice", "a styled button". Ask what layout, what colour role, what spacing scale."small" could be --space-2 or --space-3. Ask which step; never coin-flip a design system.hover, focus, active, and disabled to emit. When the description already names its states ("a button with a hover state"), emit exactly those and ask nothing.@container versus @media. When the description already says ("adapts to its container", "stacks below 768px"), take it at its word and do not ask.When no trigger fires, emit directly — a well-specified request must not be interrogated. Nothing is emitted until the answers resolve.
inline-style-to-class): on a same-name-different-value clash, append a numeric suffix -2, -3, … until unique, and report the suffixing in the summary.**/*.{css,scss} (excluding node_modules, .git, dist, build) and match the dominant flavour. No stylesheet at all means plain CSS..sass (indented syntax) is never an append target. This skill emits brace-and-semicolon blocks, which are invalid in indented Sass. Exclude .sass from the inference glob; if the user names a .sass file as the target, refuse the append with a one-line reason and print the rule instead.Fires when the user says inline or points at an element.
Refuse inline mode, with a one-line reason, and fall back to class mode, when the description implies any of:
:hover:focus-visible@media@container@layer@supports::before, ::after, …)An inline style attribute cannot carry any of these, and a dropped state is lost without an error.
Sits between token resolution and emit.
:focus-visible rule alongside the requested styling.Parity with create-utilities Step 4: without it, a generated button rule ships with no focus indicator and fails WCAG 2.4.7.
Sits between the references and emit. Baseline status decides whether a modern feature is emitted bare or wrapped — the three states and the decision rule are defined in references/progressive-enhancement.md, which this gate loads whenever it fires.
Fires only for modern features — @container, :has(), @layer, popover, field-sizing, text-wrap: balance, @starting-style, subgrid, anchor positioning, and their kin. Flexbox, border-radius, and custom properties are decades old; do not narrate their status.
Three outcomes:
@supports block containing the modern declaration, and name the fallback in the summary. Order is load-bearing: a fallback placed after the @supports block has equal specificity and later source order, so it overrides the enhancement in exactly the browsers that support it — the upgrade silently never applies. Fallback above, enhancement below, always. In inline mode, which cannot carry @supports, this is a refusal trigger under the Inline mode rules.Naming a feature is not an override. A request that says "using field-sizing" is describing intent, not waiving the gate — it is the ordinary case, and it still resolves to the outcome above. Downgrade an outcome only when the user explicitly overrides in words ("emit it anyway", "I don't care about Firefox", "skip the @supports"), and then downgrade by exactly one step: outcome 3 becomes outcome 2 (wrapped in @supports with a fallback), never straight to bare. Nothing reaches bare emit from Limited. Record the override and the step taken in the summary's Baseline line.
Read the project's target first. Check, in Browserslist's own lookup order, for a browserslist key in package.json, a .browserslistrc, or a plain browserslist file. Name the detected target in the summary.
A declared target never relaxes an outcome — it can only tighten one. The temptation is to read last 2 versions as "this project has opted into a looser bar, so skip the @supports." That is unsound in both directions:
last 2 versions includes each browser's previous release, which may predate that landing. Emitting bare would drop the fallback for users the project explicitly still targets.last 2 versions includes a release that will never support it. A loose target grants no coverage whatsoever.So a looser-looking target changes nothing: apply the outcomes as written. A target stricter than the outcomes assume (a pinned modern-evergreen list, an explicit baseline widely available) is worth naming in the summary, but it also never licenses dropping a fallback. Only an explicit user override moves an outcome, per the rule above.
Do not assert dates from memory. Baseline dates are revised — the Popover API's was corrected by nine months after publication. State the status level, not the month it landed. When status is genuinely uncertain, say so and point at webstatus.dev rather than guessing.
Grep the project for custom-property declarations (^\s*(--[A-Za-z0-9_-]+)\s*:\s*([^;\n]+);?) across the same glob set, and read the whole scale before mapping a relative word like "small" — "small" is a position in a scale, not a value.
Three tiers:
--space-2 for a small gap): use it and name the choice in the summary.Never create a new custom property unless the user explicitly asks for one. This deliberately inverts inline-style-to-class, which creates variables freely because there the value is already committed to the user's markup; here the request is authoring from scratch and inventing tokens would write into someone's design system on a throwaway request.
Loaded by model judgment when the parsed intent touches their topic — not via a keyword trigger table, since no keyword table anticipates every phrasing. The summary must name which of these were consulted.
| Reference | Covers |
|---|---|
| ${CLAUDE_PLUGIN_ROOT}/skills/css/references/container-queries.md | container-type on the parent, a container cannot query itself, cqi/cqb units, container-name. |
| ${CLAUDE_PLUGIN_ROOT}/skills/css/references/cascade-layers.md | Unlayered styles outrank layered ones, @layer order declared first, third-party CSS, !important inversion. |
| ${CLAUDE_PLUGIN_ROOT}/skills/css/references/responsive-text.md | clamp() with a rem addend, pure-vw failing WCAG 1.4.4 at 200% zoom, slope formula, text-wrap. |
| ${CLAUDE_PLUGIN_ROOT}/skills/css/references/progressive-enhancement.md | Baseline statuses and the emit-bare/wrap/withhold rule, @supports detecting upward rather than not-detection cascading down, prefers-reduced-motion, prefers-contrast. |
| ${CLAUDE_PLUGIN_ROOT}/skills/css/references/logical-properties.md | inline-size, block-size, margin-inline, padding-block, inset, writing-mode rationale. |
| ${CLAUDE_PLUGIN_ROOT}/skills/css/references/modern-selectors.md | :has() restrictions, :is()/:where()/:not() specificity, :nth-child(An+B of S), native nesting vs SCSS &. |
| ${CLAUDE_PLUGIN_ROOT}/skills/css/references/state-selectors.md | :user-invalid over :invalid, [aria-disabled="true"] over :disabled, :focus-visible rationale, :empty, :placeholder-shown. |
| ${CLAUDE_PLUGIN_ROOT}/skills/css/references/viewport-units.md | dvh/svh/lvh versus vh under mobile browser chrome, 100vw overflow with a scrollbar. |
Print one concise block containing:
<path>) or inline, plus the one-line reason if inline mode was refused.-2/-3 collision suffix that was applied.value → var(--name) reuse, naming any tier-2 semantic-name choice explicitly, and any literal emitted because nothing matched. State that no new custom property was created.:focus-visible rule emitted, or a warning that the mode cannot carry one.@supports with the named fallback (Newly available), or withheld (Limited). Name the project's detected browser target, or state that none was declared. Omit this line entirely when the rule uses no modern features.Every result closes with two or three concrete next moves drawn from what was actually emitted — the answer is already delivered, so the offer costs the user nothing. Draw from what the rule contains, for example:
Tighten the gap to var(--space-1)?Add a hover state?Switch to an inline style attribute?Promote it to a named class in your stylesheet — run /inline-style-to-class.Never offer a move the emitted rule does not support (e.g. do not offer to switch to inline when inline mode was refused).
development
Internal orchestrator for /kit-create, /kit-list, /kit-sync, /kit-update and Form/HTML/Style-Tune modes. Per-component generation lives in component-<name> skills; do not auto-trigger for component requests.
data-ai
Use when the user asks to generate, create, or scaffold a Table — accessible data table with caption, scope headers, responsive scroll wrapper, and sortable column support.
tools
Use when the user asks to generate, create, or scaffold a Popover — accessible tooltip/popover using the Popover API with focus trap, aria-expanded, and light-dismiss.
tools
Use when the user asks to generate, create, or scaffold a Nav — accessible navigation landmark with aria-label, current-page link marking, and horizontal/vertical layout.