# Css Inspector

> Launch a visual CSS inspector panel on any HTML project. Triggers on: "inspect my page", "visual CSS editor", "let me tweak the styles", "open the CSS inspector", "I want to edit styles visually

- **Type:** Skill
- **Install:** `agentstack add skill-aviranrevach-css-inspector-skill-css-inspector-skill`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [aviranrevach](https://agentstack.voostack.com/s/aviranrevach)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [aviranrevach](https://github.com/aviranrevach)
- **Source:** https://github.com/aviranrevach/css-inspector-skill

## Install

```sh
agentstack add skill-aviranrevach-css-inspector-skill-css-inspector-skill
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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:

```bash
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:
```bash
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.

1. **Read `package.json`** (if present) and check `dependencies` + `devDependencies` for 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) |

2. **Look for corroborating project files** to upgrade confidence:
   - `components.json` at the project root → strong shadcn signal
   - `src/components/ui/*.tsx` files that import from `class-variance-authority` → strong shadcn signal
   - `tailwind.config.{js,ts,mjs}` → tailwind confirmed

3. **Classname-only fallback** (when there's no `package.json`, e.g. static HTML): grep the rendered HTML for class prefixes:
   - `ant-*` → antd
   - `chakra-*` → chakra
   - `MuiButton-*` or emotion `css-*` patterns → mui
   - lots of `bg-*`, `text-*`, `rounded-*` utilities → tailwind

4. **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).

5. **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.

6. **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."

1. **Scan the source files** for component definitions. Prioritize, in order:
   - `*.jsx` / `*.tsx` files in `src/`, `app/`, `components/`, or the project root
   - `*.vue` / `*.svelte` files if present
   - `*.html` files 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.

2. **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.

3. **Verify against the live DOM (recommended).** A pure source scan often produces incorrect class fragments (e.g. `filter-bar` vs `filterbar`, `src-chip` vs `src-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.

4. **Write `.inspector/design-system.json`** with this shape (matches `presets/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 class `foo` (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 a `prefix-value` convention like `tier-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)

5. **Set `preset: "custom"`** in `settings.json` and inline the new `design-system.json` into the `manifest` field. Add a `customLabel` field with a short project name (e.g. `"Pulse for Product"`); the Settings panel will display it on the Import card.

6. **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.json` to refine matches; reload the inspector to apply."*

## Step 4a — Static mode setup

1. Read `index.html` and all linked CSS/SCSS files.
2. Build a `cssMap` object mapping each `selector → property → { file, line }`. Example:
   ```json
   { ".hero-title": { "font-size": { "file": "styles.css", "line": 24 } } }
   ```
3. Create `.inspector/` directory in project root.
4. Copy `overlay.js` and `server.py` from the skill folder (`~/.claude/skills/css-inspector/`) into `.inspector/`.
5. Write `.inspector/inspector.html`:
   ```html
   
   
   Inspector
   
     
       window.__inspectorCssMap   = CSS_MAP_JSON_HERE;
       window.__inspectorSettings = SETTINGS_JSON_HERE;
     
     
     
   
   
   ```
   Replace `CSS_MAP_JSON_HERE` with the JSON-stringified cssMap, and `SETTINGS_JSON_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.

6. Kill any process on port 8787: `lsof -ti:8787 | xargs kill -9 2>/dev/null || true`
7. Start server: `python3 .inspector/server.py 8787 . &`
8. Output: **Open http://localhost:8787/.inspector/inspector.html to start inspecting.**

## Step 4b — Live mode setup

1. Detect framework web root:
   - Check for `vite.config.*` → root is project root
   - Check for `public/index.html` (CRA / Next.js) → root is `public/`
   - Default: project root
2. **Always re-copy** `overlay.js` from `~/.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.
3. **Always re-copy** any `presets/` files referenced by `.inspector/settings.json` so design-system data is fresh too.
4. Find the HTML entry point (`index.html` in project root, or `public/index.html`)
5. Inject before ``:
   ```html
   
   window.__inspectorSettings = SETTINGS_JSON_HERE;
   
   
   ```
   Replace `SETTINGS_JSON_HERE` 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.
6. 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 `file` is set:** Open that file. Find the CSS rule for `selector`. Update the `property` value to `to`. If `line` is provided, start searching near that line.
- **If `file` is null:** Search the codebase for where `selector` is 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:
```json
{ "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:

1. **`text` match (preferred when present):** keep candidates whose surrounding JSX contains the `text` value (or a normalized version — strip leading punctuation/icons, collapse whitespace). For `text: "+ Promote to backlog"`, match against the JSX literal "Promote to backlog".
2. **`domIndex` fallback (preferred for icon-only / textless elements):** if `text` is 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).
3. **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..detect` rules use `hasClass` or `if` (classname-driven)** — typical for hand-authored design systems like Pulse:
  1. Find the JSX element matching `selector` in source. Use the `source` hint (file path + line) to narrow the search.
  2. In the element's `className` (string, template literal, or `clsx` call), remove the class fragment that signaled `from` and add the class fragment for `to`. Find both fragments in the manifest's `detect` rules (`hasClass: "primary"` → the literal class `primary`; `if: "tier-pro"` → that exact class).
  3. 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](https://github.com/aviranrevach)
- **Source:** [aviranrevach/css-inspector-skill](https://github.com/aviranrevach/css-inspector-skill)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-aviranrevach-css-inspector-skill-css-inspector-skill
- Seller: https://agentstack.voostack.com/s/aviranrevach
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
