Install
$ agentstack add skill-aviranrevach-css-inspector-skill-css-inspector-skill ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
CSS Inspector Skill
When this skill is triggered, follow these steps exactly.
Step 1 — Clean up any previous session
Search the project for leftover injection markers and remove them:
grep -rl "css-inspector:start" . --include="*.html" 2>/dev/null
For each file found, remove the block between ` and ` (inclusive).
Step 2 — Gitignore setup
Check if .gitignore exists. If so, add .inspector/ if not already present. If not, create it with .inspector/.
Step 3 — Detect project type
Live mode: Check if a dev server is running on common ports:
lsof -i :3000 -i :5173 -i :4200 -i :8080 | grep LISTEN
If found → live mode with that port.
Static mode: If no dev server found and index.html exists in the project → static mode.
Ambiguous: Ask the user: "Is there a dev server running, or should I serve the HTML files directly?"
Step 3.5 — Design system detection (always run)
Detect which design system (if any) the project uses. The result is written to .inspector/settings.json and consumed by the inspector to power the "Component" section of the Design tab.
- Read
package.json(if present) and checkdependencies+devDependenciesfor these fingerprints:
| System | Signal in deps | |---|---| | shadcn | any @radix-ui/* package and class-variance-authority | | mui | @mui/material or @mui/joy | | chakra | @chakra-ui/react | | mantine | @mantine/core | | antd | antd | | nextui | @nextui-org/react | | tailwind | tailwindcss (devDependencies counts) |
- Look for corroborating project files to upgrade confidence:
components.jsonat the project root → strong shadcn signalsrc/components/ui/*.tsxfiles that import fromclass-variance-authority→ strong shadcn signaltailwind.config.{js,ts,mjs}→ tailwind confirmed
- Classname-only fallback (when there's no
package.json, e.g. static HTML): grep the rendered HTML for class prefixes:
ant-*→ antdchakra-*→ chakraMuiButton-*or emotioncss-*patterns → mui- lots of
bg-*,text-*,rounded-*utilities → tailwind
- Pick a winner by confidence:
- High: deps signal + at least one corroborating file or matching classnames
- Medium: deps signal alone
- Low: only classnames
- If multiple match (e.g. tailwind + shadcn), prefer the higher layer (shadcn over tailwind).
- Build
settings.json. If the winner has a preset shipped under~/.claude/skills/css-inspector/presets/.json, read it and inline its manifest:
``json { "detection": { "detected": [ { "system": "shadcn", "confidence": "high", "signals": ["@radix-ui/react-slot in deps", "components.json at root", "src/components/ui/button.tsx uses cva"] } ], "recommended": "shadcn" }, "preset": "shadcn", "manifest": { /* contents of presets/shadcn.json, inlined */ } } ``
If no system matched: recommended: null, preset: "claude" (Claude design — Claude identifies components on the fly), manifest: { "components": [] }.
Valid preset values: "claude", "shadcn", "mui", "chakra", "mantine", "antd", "nextui", "tailwind", "custom", "none". Picking "none" disables the Component section entirely. Picking "claude" skips the manifest and surfaces an "Ask Claude" action for every pick.
- Write the file:
.inspector/settings.json. Create.inspector/if it doesn't exist (it normally will by the time step 4a/4b runs, but this step can come first).
Step 3.6 — Custom design-system manifest (run when no preset matched)
Trigger: Step 3.5 wrote "recommended": null (no known design system detected) and the project has source files that look hand-authored (custom React/JSX/Vue/Svelte/etc.). Skip this step if the recommended preset is one of the shipped ones — that preset's manifest already covers detection.
The goal: build a design-system.json describing the project's components so the inspector's Component section can identify them by classname instead of always falling back to "Ask Claude."
- Scan the source files for component definitions. Prioritize, in order:
*.jsx/*.tsxfiles insrc/,app/,components/, or the project root*.vue/*.sveltefiles if present*.htmlfiles with non-trivial markup (for static prototypes)
For each file, find:
- Component declarations (
function ComponentName(...),const ComponentName = (...) =>,export function,export default function) - The root JSX element's
className— note all classname fragments, especially those that look like component identifiers (card,chip,pill,btn,*-card, etc.) - Conditional classnames driven by props (
className={\base ${variant === 'foo' ? 'class-a' : 'class-b'}\},clsx(...), template strings) — these are variant signals.
- Pick component-worthy entries. Keep only components that:
- Have at least one distinctive classname on the root element (a class that wouldn't match unrelated components)
- Are reusable enough to appear more than once, or are visually meaningful even as a one-off (cards, headers, large layout regions are fine even as singletons)
Skip pure layout wrappers and one-line passthroughs with no classnames.
- Verify against the live DOM (recommended). A pure source scan often produces incorrect class fragments (e.g.
filter-barvsfilterbar,src-chipvssrc-pill). If you have a way to render the prototype briefly:
- Open the static HTML / dev server and let it hydrate
- Enumerate the actually-rendered classnames on element samples
- Cross-check the source-derived names against the rendered classes; fix any mismatches before writing the manifest
If you can't render the page, write the manifest from source alone but mark uncertain entries with a "$confidence": "low" field — the user can refine later.
- Write
.inspector/design-system.jsonwith this shape (matchespresets/shadcn.json):
``json { "system": "custom", "label": " (custom)", "description": "Generated by scanning .", "components": [ { "name": "Button", "tag": "button", "anyClass": ["btn"], "source": "src/components/Button.tsx", "props": { "variant": { "values": ["default","primary","secondary","ghost"], "default": "default", "detect": [ { "hasClass": "primary", "value": "primary" }, { "hasClass": "secondary", "value": "secondary" }, { "hasClass": "ghost", "value": "ghost" } ] } } } ] } ``
Match rule reference (use the strictest rule that fits — exact match preferred):
"anyClass": ["foo"]— matches if the element has the exact classfoo(any of the list)"allClass": ["foo","bar"]— matches only if both classes are present"anyClassContains": ["foo"]— matches if any class contains the substring (looser; use only when class names follow aprefix-valueconvention liketier-pro)"allClassContains": ["foo"]— same but requires all"tag": "button"— combine with class rules to scope
Detect rule reference (for props..detect):
{ "hasClass": "primary", "value": "primary" }— exact-class match (preferred — required for live class swapping to work cleanly){ "if": "tier-pro", "value": "pro" }— substring match (loose; fine for unique prefix conventions)
- Set
preset: "custom"insettings.jsonand inline the newdesign-system.jsoninto themanifestfield. Add acustomLabelfield with a short project name (e.g."Pulse for Product"); the Settings panel will display it on the Import card.
- Tell the user what was found. After writing the manifest, surface a one-liner like: "Generated a custom design-system manifest with N components for . Edit
.inspector/design-system.jsonto refine matches; reload the inspector to apply."
Step 4a — Static mode setup
- Read
index.htmland all linked CSS/SCSS files. - Build a
cssMapobject mapping eachselector → property → { file, line }. Example:
``json { ".hero-title": { "font-size": { "file": "styles.css", "line": 24 } } } ``
- Create
.inspector/directory in project root. - Copy
overlay.jsandserver.pyfrom the skill folder (~/.claude/skills/css-inspector/) into.inspector/. - Write
.inspector/inspector.html:
```html
Inspector
window.__inspectorCssMap = CSSMAPJSONHERE; window._inspectorSettings = SETTINGSJSONHERE;
`` Replace CSSMAPJSONHERE with the JSON-stringified cssMap, and SETTINGSJSON_HERE with the contents of .inspector/settings.json` written in step 3.5.
The overlay is iframe-aware: it detects the iframe, waits for it to finish loading, and binds picker listeners to the iframe's contentDocument. The script tag and iframe can appear in either order.
- Kill any process on port 8787:
lsof -ti:8787 | xargs kill -9 2>/dev/null || true - Start server:
python3 .inspector/server.py 8787 . & - Output: Open http://localhost:8787/.inspector/inspector.html to start inspecting.
Step 4b — Live mode setup
- Detect framework web root:
- Check for
vite.config.*→ root is project root - Check for
public/index.html(CRA / Next.js) → root ispublic/ - Default: project root
- Always re-copy
overlay.jsfrom~/.claude/skills/css-inspector/into/.inspector/overlay.js— overwrite any existing copy. This ensures every "open the inspector" run gets the latest skill code; otherwise users hit stale-snapshot bugs when the skill is updated but their projects still hold the old overlay. - Always re-copy any
presets/files referenced by.inspector/settings.jsonso design-system data is fresh too. - Find the HTML entry point (
index.htmlin project root, orpublic/index.html) - Inject before ``:
```html
window.__inspectorSettings = SETTINGSJSONHERE;
`` Replace SETTINGSJSONHERE with the inline JSON contents of .inspector/settings.json written in step 3.5. If the markers already exist (re-trigger of a project that's been inspected before), leave the injection in place and just refresh the overlay.js and settings.json` content.
- Output: Inspector injected. Open your dev server (http://localhost:PORT) to start inspecting. The panel will appear in the top-right corner.
Step 5 — Wait for user to finish
Tell the user:
- The panel docks to the top-right. Drag the header to move it; the bottom-left handle resizes it; the
—button minimizes it to the header bar. - Click the Select button (top-left of the header), then click any element on the page. The selector pill at the top shows what's currently selected. Right-click a picked element to open the element-tree popup for navigating parents and siblings.
- Talk to Claude about the selection. After picking, click the selector pill in the header — the element-tree popup opens with a 📋 Copy chat-ready intro link at the top. Clicking it puts a chat-ready intro on your clipboard (
Let's talk about this element \.hero-title\(h1):for leaf elements,Let's talk about this area \.hero\(section):for containers). Paste it into your next Claude message, then type the ask. Claude now has the selector unambiguously. (The ✕ next to the selector pill clears the current selection.) - Edit in the Design tab — collapsible sections for Position (X/Y/Z, rotation, flip), Layout (flow, dimensions, padding/margin diagram, clip/border-box), Appearance (opacity, radius, fill, stroke, shadow), and Typography (font family, size, weight, line height, color). All edits preview live. The color picker supports solid and linear-gradient with eyedropper.
- Use the CSS Raw tab to edit matched stylesheet rules as plain text and click Apply to tracker.
- The bottom Changes bar shows undo/redo and a "Changes to execute" pill. Click the pill to expand the list of tracked edits, then click Copy Prompt.
- Paste the copied prompt back into this chat.
When the user pastes a prompt containing either a ` block or a ` block, proceed to Step 6. A pasted prompt may contain one or both blocks.
Step 6 — Apply changes to source
The Copy Prompt can carry three payloads:
- `` — raw CSS edits the user made in the Design / CSS Raw tabs.
- `` — design-system intents (variant swaps, component conversions) the user picked from the Component section.
- `` — sibling reorders (arrow-key nudges or drag-drops) the user made on the live DOM.
Handle whichever blocks are present. Apply order: CSS changes first → component intents → reorders. CSS first so classname swaps operate on the latest source; reorders last because they may move elements out from under earlier edits.
6a · Apply `` (CSS edits)
Parse the ` JSON block. Each entry is { selector, property, from, to, file, line }`.
- If
fileis set: Open that file. Find the CSS rule forselector. Update thepropertyvalue toto. Iflineis provided, start searching near that line. - If
fileis null: Search the codebase for whereselectoris defined. Check CSS/SCSS files first. If found in a component file (CSS-in-JS, CSS Module, Vue/Svelte scoped styles), find the declaration and update it. If the style comes from an external/CDN stylesheet, add an override rule to the project's main CSS file.
6b · Apply `` (design-system intents)
Parse the ` JSON block. Each entry has an action field; handle the two actions below. Look up the active manifest from .inspector/settings.json` so you know how the component's variants are signaled (classname vs. prop).
Action: swap-variant
Shape:
{ "action": "swap-variant", "selector": ".my-btn", "component": "Button",
"prop": "variant", "from": "primary", "to": "destructive",
"text": "Promote to backlog", "domIndex": 1, "source": "src/page.tsx:34" }
text and domIndex are pinpointing hints emitted by the inspector — they let you choose the right element when the bare selector matches several source locations.
Source disambiguation (before either strategy below): grep the candidate file for the element's signal. Filter the matches in this order, stopping when you have exactly one:
textmatch (preferred when present): keep candidates whose surrounding JSX contains thetextvalue (or a normalized version — strip leading punctuation/icons, collapse whitespace). Fortext: "+ Promote to backlog", match against the JSX literal "Promote to backlog".domIndexfallback (preferred for icon-only / textless elements): iftextis absent or didn't narrow to one, pick the Nth match in source-document order where N =domIndex(e.g., the second `` in the file).- Still ambiguous → surface a TODO instead of guessing: "Found 3 candidates for
.my-btn; please confirm which one."
Two strategies depending on how the manifest signals the variant — inspect the matching component entry in the manifest:
- If the manifest's
props..detectrules usehasClassorif(classname-driven) — typical for hand-authored design systems like Pulse:
- Find the JSX element matching
selectorin source. Use thesourcehint (file path + line) to narrow the search. - In the element's
className(string, template literal, orclsxcall), remove the class fragment that signaledfromand add the class fragment forto. Find both fragments in the manifest'sdetectrules (hasClass: "primary"→ the literal classprimary;if: "tier-pro"→ that exact class). - If the className is built from a prop or variable (e.g.,
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: aviranrevach
- Source: aviranrevach/css-inspector-skill
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.