agentsclimarketplace

Recomponentize ui

Skill ai-hero/hero-skills/skills/recomponentize-ui

Refactor a project's UI into atomic components (atoms/molecules/organisms/templates), sourcing primitives from a design-system registry when one is configured (default @aihero) or stock shadcn otherwise, and codemod off-token styling. Use when asked to recomponentize, refactor UI, adopt a design system, or clean up component structure.From its SKILL.md

Install
npx -y skills add ai-hero/hero-skills --skill recomponentize-ui

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

17.2 KB, as published. Nobody here has run it

Recomponentize UI — Atomic Components, Design-System Sourced

Refactor an app's UI into a proper atomic component hierarchy, and stop hand-rolling primitives that already exist upstream.

Two things always happen: components get recomponentized into atomic layers, and off-token styling gets codemodded. A third happens when a registry is available: local primitives get replaced by registry components.

Pipeline DAG

preflight → enforce → inventory → map → install → recomponentize → codemod → verify

Print the DAG line at the start of each step:

[N/8] (✓) preflight → (▶) enforce → ( ) inventory → ( ) map → ( ) install → ( ) recomponentize → ( ) codemod → ( ) verify

Now running: enforce

Enforcement is installed early, before any code is rewritten, so the path-scoped rule guides the migration itself rather than only future work.

When no registry is configured, map and install still run — against stock shadcn or the project's existing UI library instead of a private registry.

Arguments

  • $ARGUMENTS:
    • (none) — full pass using the component source resolved in Step 0
    • --audit-only — run preflight, inventory, map; report the plan, change nothing. Skips enforce too: installing the rule and hook writes files, which --audit-only promises not to do.
    • REGISTRY_NAMESPACE — override the registry (e.g. @acme)

Step 0: Resolve the component source

Producer repos must opt out — check this first

If HERO.md says role: producer (or enabled: false) under ## Design System, stop immediately. Report that this repo publishes the design system and exit without changing anything. A registry repo's pipeline runs mockup → design system; running this skill there would invert it and try to consume its own output.

ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
# Reads a whole SECTION, not a scalar field — hero_field cannot express this.
sed -n '/## Design System/,/^## /p' "$ROOT/HERO.md" 2>/dev/null `# hero-lint: allow-inline` || echo "NO_DESIGN_SYSTEM_CONFIG"

A repo is a producer if it builds a registry.json, serves /r/*, or its components.json aliases ui to an internal atomic directory rather than @/components/ui. If those signals are present but HERO.md says consumer, trust the signals, stop, and tell the user HERO.md looks wrong.

Pick the component source

Resolve in this order and state which one you picked before proceeding:

ConditionSource
HERO.md ## Design System present with namespaceThat registry
No config, but the user wants oneOffer @aihero / https://design.aihero.studio — needs a token (Step 1)
No registry, components.json existsStock shadcn — npx shadcn@latest add ITEM
No registry, another UI lib in package.json (MUI, Chakra, Mantine, Ant)That library's primitives; do not migrate libraries uninvited
Nothing — plain HTML/CSSRecomponentize and codemod only; ask before introducing any dependency

Expected HERO.md keys: namespace, registry-url, token-env-var, docs, atomic-layers. AI Hero defaults:

  • namespace: @aihero
  • registry-url: https://design.aihero.studio/r/{name}.json
  • token-env-var: REGISTRY_TOKEN
  • docs: https://design.aihero.studio

If the registry publishes a consumer handbook, read it first and let it win over this skill. For @aihero that is handbook/consuming-the-registry.md in the ai-hero/design-system repo — it ships a canonical consumer AGENTS.md stanza and consumer SKILL.md. Install those rather than re-deriving them.

Never introduce a UI library into a project that has none without asking. The atomic refactor is valuable on its own and carries no new dependency.

Step 1: Preflight (preflight)

Skip to Step 2 when the source is "recomponentize only" — the enforcement layer still applies. Otherwise, never run npx shadcn init on an existing project — it does not add the registries block and may pick a conflicting style.

CheckRequirement
components.jsonHas a registries block for the namespace; "ui": "@/components/ui"
Token expansionHeader is Bearer ${REGISTRY_TOKEN} — the plain form ONLY
.envHolds the token; .gitignore covers .env BEFORE the token is written
src/lib/utils.tsExports cn (clsx + tailwind-merge)
tsconfig.jsonbaseUrl: "." and paths: { "@/*": ["./src/*"] }
CSS entryImports tailwind (v4); named in components.json → tailwind.css
Runtime depsreact@^19, react-dom@^19, tailwindcss@^4, clsx@^2, tailwind-merge@^3, shadcn@^4.13.0

Trap — never write ${VAR:-default}. The CLI's expansion regex is /\$\{(\w+)\}/g, so the default form ships as a literal header string and surfaces as a confusing 401 instead of a missing-variable error.

Trap — do not copy the design system's own components.json. A registry repo's config points at localhost and aliases ui to its internal atomic dir. Those are wrong in a consumer.

If the project has a committed .env, rename it to .env.example, strip the value, and gitignore the real one. Never commit a token.

Trap — .env must sit next to components.json, not in the cwd. Verified empirically against shadcn 4.13:

.env locationcwdResult
repo rootrepo root, -c uifails
repo rootui/fails
ui/repo root, -c uiworks
ui/ui/works
none, REGISTRY_TOKEN exportedanywhereworks

So in a monorepo whose UI lives in ui/, the token goes in ui/.env even when the repo's house convention keeps every other secret in a root .env. A root .env produces Set the required environment variables to your .env or .env.local file — which reads like a missing variable rather than a wrong-directory problem, so it is easy to misdiagnose. Exporting the variable in the shell or a task-runner recipe also works and beats duplicating the secret.

Confirm the wiring before going further — install the theme item first so tokens land before any component references them:

npx shadcn@latest add NAMESPACE/theme
grep -- "--primary" src/styles.css

Step 2: Install enforcement (enforce)

A skill only fires when the model chooses it, and model-discretion triggering is least reliable for exactly this kind of well-trained task. These layers do not depend on that choice — install both:

"$PLUGIN_ROOT/scripts/install-design-system.sh" "$ROOT"

It writes, without overwriting customized files (exit 2 on drift, same contract as install-auto-approve.sh):

  • .claude/rules/design-system.md — path-scoped to **/*.{tsx,jsx,css}, so the constraints load whenever Claude reads a UI file rather than when a description happens to match.
  • .claude/hooks/check-design-tokens.sh + PostToolUse wiring — flags raw hex, palette classes, and component-root margins on write.

Then add the registry's AGENTS.md stanza (Part 8 of the @aihero handbook) and the consumer SKILL.md (Part 9). If the registry ships them, paste verbatim.

Optionally port the registry's lint config (for @aihero, eslint.taste.config.mjs — Tailwind correctness, token discipline, a11y floor). Its atomic-boundaries block does apply once Step 6's layers exist; add ui and blocks as the lowest elements in the layer matrix.

Step 3: Inventory the current UI (inventory)

Find every hand-rolled element, every oversized component, and every off-token style. Use an Explore subagent for a large codebase.

# Hand-rolled primitives that likely exist upstream
grep -rnE '<(button|input|select|textarea|table|dialog|nav|header|footer)\b' --include="*.tsx" --include="*.jsx" src/

# Recomponentization candidates — size outliers
find src -name "*.tsx" -exec wc -l {} + | sort -rn | head -20

# Off-token styling — the codemod targets from Step 7
grep -rnE 'className="[^"]*\b(bg|text|border|ring)-(slate|gray|zinc|neutral|stone|red|blue|green|amber)-[0-9]' --include="*.tsx" src/
grep -rnE '#[0-9a-fA-F]{3,8}\b|oklch\(' --include="*.tsx" --include="*.ts" src/
grep -rnE 'className="[^"]*\bshadow-|z-\[|rounded-\[|text-\[[0-9]' --include="*.tsx" src/
grep -rnE 'className="[^"]*\bdark:(bg|text|border)-' --include="*.tsx" src/

Record for each finding: file, line, what it is, and the UI concept it expresses ("a primary action", "a labelled form field with error text"). The concept — not the markup — is what you search for in Step 4.

Step 4: Map local → upstream (map)

For every concept in the inventory, find its upstream equivalent. Use both paths — they surface different things:

Search the catalog (finds by category keyword):

npx shadcn@latest search NAMESPACE -q "form"
npx shadcn@latest view NAMESPACE/field    # inspect the API before committing

There is no --registry flag; registries come only from components.json. For @aihero the full catalog is GET /r/registry.json — there is no /r/index.json.

Browse the docs/gallery site (finds by appearance): open it with the browser tools and look at the rendered components. Search matches keywords; the gallery matches look. A local "stat card" may be a tile or a kpi-strip — only the gallery makes that obvious. For stock shadcn, use ui.shadcn.com/docs/components.

Optionally wire the registry's MCP server for in-editor search:

npx shadcn@latest mcp init --client claude

It reads components.json for registries and auth headers — no separate MCP auth. If /mcp reports no tools, run npx clear-npx-cache.

Produce a mapping table and show it to the user before installing:

LOCAL                          → UPSTREAM ITEM        CONFIDENCE  NOTE
src/components/Btn.tsx         → @aihero/button       high        variant=default|destructive
src/components/StatCard.tsx    → @aihero/tile         medium      confirm against gallery
src/components/Wizard.tsx      → (none)               —           keep; recomponentize only

Items with no upstream equivalent stay local — they still get recomponentized (Step 6) and codemodded (Step 7). Do not force a bad match.

Step 5: Install and rewrite call sites (install)

npx shadcn@latest add NAMESPACE/button NAMESPACE/card
npx shadcn@latest add NAMESPACE/button --dry-run --diff   # review a re-add

registryDependencies resolve transitively — asking for field also brings label and separator. add defaults to --overwrite false.

Installed files are vendored, not authored. This is the rule that matters most: rewrite call sites, never the vendored files. A local edit to components/ui/* is silently reverted by the next shadcn add --overwrite. To change a vendored component: compose around it, pass className (every item merges via cn()), or change it upstream and re-add.

Delete the local component only after its last call site is migrated and typecheck passes.

Step 6: Recomponentize (recomponentize)

This step always runs — it is the point of the skill. It runs for components with no upstream match, for projects with no registry at all, and for the app code that composes vendored primitives. Swapping in new components without recomponentizing leaves the same monolith wearing new classes.

Target layout — vendored code stays flat; the app's own components go atomic:

src/components/
├── ui/          # vendored primitives (registry or shadcn) — never edit
├── blocks/      # vendored sections                        — never edit
├── atoms/       # app-specific primitives upstream lacks (should be rare)
├── molecules/   # compositions of ui/ + atoms/
├── organisms/   # domain-aware sections; may import domain types
└── templates/   # layout shells with slot props — structure only, zero copy

Layer rules, enforced in review:

  • atoms / molecules are stateless and generic: no fetching, no auth, no domain types. Props in, UI out. Every one accepts and merges className.
  • organisms may import domain types and compose molecules — still no data fetching; data arrives via props.
  • templates define where things go via slot props (header, sidebar, children); they never hardcode copy or fetch data.
  • Imports flow downward only: templates → organisms → molecules → atoms → (ui/, blocks/). ui/ and blocks/ are the floor — any layer may import them. An atom importing a molecule is a defect; restructure instead of suppressing it.
  • Same-layer imports only for family relationships. Test: can you describe the importer without naming a different concept? "A row of buttons" is still buttons → atom. The moment a component combines distinct siblings (input + button = search-bar), it belongs one layer up.
  • Place each component at the lowest layer that fits. Promote only when it gains domain knowledge or composition — never preemptively.

Signals a component needs recomponentizing: boolean-prop explosion, a molecule fetching data, two organisms sharing copy-pasted JSX, a component importing from a higher layer, a file well above the codebase's median length.

Migrate call sites as you move files; never leave a re-export shim behind as "temporary" — finish the move or don't start it.

Step 7: Codemod off-token styling (codemod)

FoundReplace with
Raw palette (bg-zinc-100, text-gray-500)Semantic token (bg-muted, text-muted-foreground)
Hex / oklch() literal in TSXA token in the @theme layer
dark: color overrideDelete it — a dark: color means the wrong token was used
Margin on a component root (m-*, mt-*, ms-*)gap-* / space-* on the parent; parents own layout
Arbitrary spacing (p-[13px], gap-[7px])The spacing scale; if a step is missing, change the scale
z-[9999]The named z-scale (z-dropdown < z-sticky < z-overlay < z-modal < z-toast)
rounded-[10px], text-[15px], any [Npx]The radius / type / spacing scale
shadow-*Remove — elevation is borders and hairlines (@aihero house rule)
Non-Lucide iconsLucide, sized size-4 / size-5, aria-hidden unless it is the only label
Text on a colored surfaceThe paired foreground token (bg-primary → text-primary-foreground)
Removed focus outlineA visible focus-visible: ring — removing one without a replacement is a defect
Opacity hack for disableddisabled: variants + aria-disabled semantics

Also: variants via cva with typed props, never ternary/string-concat className soup. Never hand-sort classes — prettier-plugin-tailwindcss owns the order.

The no-shadow rule and the exact token vocabulary are registry-specific. With stock shadcn, keep its default token names (bg-background, text-muted-foreground are shared) and do not strip shadows — that is an @aihero house rule, not a shadcn one.

Step 8: Verify (verify)

npx tsc --noEmit

Then, using the browser tools, render the migrated screens and confirm:

  1. Each replaced component renders correctly in light and dark. Dark mode is the .dark class — never prefers-color-scheme.
  2. Transitive deps arrived (installing field must also produce label.tsx and separator.tsx).
  3. grep -- "--primary" CSS_ENTRY finds the theme's variables.
  4. No visual regression on screens you did not intend to touch.
  5. No import-direction violations: nothing in atoms/ imports from molecules/ or above.

Run the project's own lint/typecheck/test commands from HERO.md before reporting done.

Report

Recomponentize UI Summary
=========================
Source:          NAMESPACE (REGISTRY_URL) | stock shadcn | recomponentize-only
Components:      N replaced upstream, M kept local
Recomponentized: N files → atoms/molecules/organisms/templates
Codemods:        N palette → token, N margins → parent gap, N arbitrary values
Enforcement:     .claude/rules/design-system.md, PostToolUse hook, AGENTS.md stanza
Verified:        tsc clean · light+dark checked on N screens · import direction clean

Unmapped (kept local, recomponentized only):
  - PATH — reason

Next step: hero-skills:push-pr

Key Principles

  • Recomponentizing is the job. Sourcing from a registry is the bonus, not the point. A pass that swaps components without restructuring has failed.
  • Search before you build. Hand-rolling an existing component is the defect this skill prevents.
  • Vendored, not authored. Rewrite call sites; never edit ui/ or blocks/.
  • The concept, not the markup. Search for what an element means.
  • Gallery and search are complementary. Search matches keywords; the gallery matches appearance.
  • Never force a match, never add a library uninvited. No upstream equivalent → keep it local, recomponentize it, and say so.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.