Ui craft
Polishes UI primitives one at a time using 2026 research-backed patterns — adds loading/icon states to buttons, replaces inline skeletons with primitives, adds secondary actions to errors, ensures 48px touch targets and visible focus indicators, applies a standard motion token ladder. Use when: 'make the UI feel more polished', 'add attention to detail', 'reduce component duplication', 'tighten the design system', 'apply craft to the frontend'.From its SKILL.md
npx -y skills add tinh2/skills-hub-registry --skill ui-craftAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 12 stars12 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
10.6 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it
You are an autonomous UI craft agent. Polish a frontend codebase one primitive at a time using the patterns below. Do NOT ask the user questions. Do NOT bundle changes into a giant PR — each primitive gets its own focused commit.
TARGET: $ARGUMENTS
If $ARGUMENTS is provided, focus on that component (e.g., Button, EmptyState).
If empty, run the full ordered pass below.
============================================================ === PRE-FLIGHT ===
- Working directory is a frontend project (React/Next/Vue/Svelte detected via package.json).
- Tests run (
npm test,pnpm test,bun test, or framework equivalent) — establish a baseline pass rate. - Lint/typecheck command available (
tsc --noEmit,eslint, etc.). - Git working tree is clean — refuse to start if dirty (offer to stash).
Recovery:
- If no test command exists: warn loudly. Continue but mark each phase "unverified."
- If tests already failing on main: stop and report the failing tests. Do not pile on.
============================================================ === PRINCIPLES (from research, 2026) ===
Apply these without re-deriving them:
The three UI states (empty / loading / error) are where polish lives.
- Empty: explain why empty, clear CTA, preview of populated state.
- Loading: skeleton screen matching final layout (not a generic spinner). Optimistic UI for likes/follows/CRUD.
- Error: plain-language what-went-wrong, what user can do, path back. Specific responses per type (403→contact, 404→alternatives, 5xx→retry, validation→highlight field).
Motion ladder. Only animate transform and opacity — these are GPU-composited. Animating layout properties (width/height/top/left/margin/padding) drops below 60fps. Wrap all animations in prefers-reduced-motion. Use three curves and four durations:
| Curve | cubic-bezier | Use |
|---|---|---|
--ease-out-expo | 0.16, 1, 0.3, 1 | modals, large transitions |
--ease-snappy | 0.4, 0, 0.2, 1 | loaders, toggles |
--ease-spring | 0.34, 1.56, 0.64, 1 | hover, success bounces |
| Duration | Value | Use |
|---|---|---|
--duration-fast | 150ms | hover, focus micro-interactions |
--duration-base | 200ms | button press, toggles |
--duration-medium | 280ms | modals, drawers |
--duration-slow | 600ms | celebrations (rare) |
Design token tiers — primitive (purple-500) → semantic (--surface-primary, --text-danger) → component (--button-radius, --card-shadow). Components should reference component-level tokens that fall back to semantic; never reference primitives directly.
WCAG 2.2 polish gates — every interactive element:
- Touch target ≥ 48 CSS px (project rule — exceeds 24px WCAG min).
- Visible focus indicator ≥ 2px perimeter, 3:1 contrast against unfocused state (2.4.13).
- Focus not entirely hidden by sticky content (2.4.11).
- Drag operations have single-pointer alternative (2.5.7).
- Auth flows: no cognitive tests; allow password managers / biometric / magic links (3.3.8).
Reusable component checklist — for each primitive:
- All states baked in: default / hover / focus-visible / active / disabled / loading / error / success.
- Variant API: primary/secondary/ghost/destructive on actions; sm/md/lg sizes.
- TypeScript-typed props enforcing valid variant combos at compile time.
- Accessibility as a primary requirement, not a checklist.
- Tests per state per variant.
============================================================ === ORDERED PASS (one component per phase, one commit per phase) ===
For each phase below: read the current primitive (if any), apply the diff sketch, add/update tests, run the test suite + typecheck, commit. Then move to the next phase.
Phase 1 — Button
Audit Button (or the most-used button primitive). Add if missing:
loading?: boolean+loadingText?: string— disables the button, setsaria-busy, renders an inline spinner.leftIcon/rightIconslots — kills inline<svg> + labelduplication.- Narrow
transition-all→ explicittransform, opacity, background-color, border-color, box-shadow, color(perf rule). focus-visible:ring-2withring-offset-2against current bg.- Verify 48 px min-height on every size variant.
Migrate consumers that hand-roll a <span className="spinner" /> + label pattern inside a <button> to use the new loading prop. Stop after 5–10 high-impact migrations; bulk migration is its own task.
Phase 2 — EmptyState
Audit EmptyState. Add if missing:
icon?: ReactNode— make optional, ship a sensible default glyph (most consumers duplicate the same inline SVG).- Normalize icon container (fixed h × w) so consumer SVGs don't need to set width/height.
tone: "default" | "subtle" | "card"— default keeps dashed border; subtle drops it; card uses solid surface.size: "default" | "compact"— compact reduces vertical padding.secondaryAction?: ReactNode— for "Learn more" or "Watch demo" links beside the primary CTA.data-empty-stateattribute for analytics/testing.
Phase 3 — Skeleton
If no Skeleton primitive exists, create one. The codebase almost certainly has inline <div className="skeleton h-X w-Y" /> (or animate-pulse bg-gray-200) scattered across many files.
// skeleton.tsx
export function Skeleton({
as: Tag = "div",
className,
"aria-hidden": ariaHidden = true,
...rest
}) {
return (
<Tag
aria-hidden={ariaHidden}
className={["skeleton", className].filter(Boolean).join(" ")}
{...rest}
/>
);
}
export function SkeletonStack({
count,
itemClassName = "h-4 w-full",
gap = 3,
label = "Loading",
}) {
return (
<div
role="status"
aria-live="polite"
aria-busy="true"
aria-label={label}
className={`flex flex-col gap-${gap}`}
>
{Array.from({ length: count }).map((_, i) => (
<Skeleton key={i} className={itemClassName} />
))}
</div>
);
}
Migrate 5–10 loading-list patterns from inline Array.from(...).map(<div className="skeleton ...">) to <SkeletonStack>. The win is consistency + the live-region announcement for screen readers.
Phase 4 — ErrorAlert (or equivalent)
Audit the error component. Add:
retryLabel?: string— default "Try again" is brittle when the action isn't a retry (e.g., "Reconnect", "Refresh data").secondaryAction?: ReactNode— for "Contact support", "Go back", or "Get help" links.- Replace
transition-colorson the retry button with the explicit transition-property list.
Phase 5 — Motion token ladder
Open the global stylesheet. Add the three easing curves and four durations from the table above as CSS custom properties (only the ones missing). Add a comment block pointing to this skill so future contributors reach for the same tokens.
Search the codebase for raw cubic-bezier(...) values and transition: ... 0.3s ease. For each, decide: replace with a token, or document why the one-off is intentional. Don't mass-migrate — leave a TODO comment with the recommended token.
Phase 6 — Focus indicators (WCAG 2.2 2.4.13)
Grep for components that use only the browser default outline:
grep -rL "focus-visible:" --include="*.tsx" src/components
For each, add focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--primary)] focus-visible:ring-offset-2 (or the framework equivalent). Run a quick contrast check on the resulting ring vs the component's resting background — if below 3:1, swap to a higher-contrast ring color.
============================================================ === PER-PHASE WORKFLOW ===
- Read the existing component (if any) and its test file.
- Write the diff. Preserve test assertions that check specific className substrings — extend, don't replace.
- Add tests for new features (each new prop gets at least one test).
- Run the focused test file:
pnpm vitest run <path>(or framework equivalent). - Run the full suite + typecheck before committing.
- Commit with conventional commits format:
feat(ui): polish Button — loading, leftIcon, rightIcon. - Keep each commit to ≤ 20 files (matches project CLAUDE.md if present).
============================================================ === STRICT RULES ===
- Never delete consumer code without checking it's actually unused.
- Never introduce a new variant or prop without a test.
- Never collapse
transition-allto nothing — replace with the explicit property list. - Never animate layout properties to "fix" a transition.
- Never assume the project has a Skeleton/EmptyState/ErrorAlert primitive — check first.
- Never skip the focus indicator audit, even if "everything looks fine."
- If a phase has no work (component already polished), record that and move on. Don't manufacture changes.
============================================================ === FINAL REPORT ===
After all phases (or after the requested phase finishes), print:
ui-craft pass complete
Phase 1 — Button: <NEW PROPS> | <N migrations> | <test delta>
Phase 2 — EmptyState: <NEW PROPS> | <N migrations> | <test delta>
Phase 3 — Skeleton: <CREATED / EXISTED> | <N migrations> | <test delta>
Phase 4 — ErrorAlert: <NEW PROPS> | <N migrations> | <test delta>
Phase 5 — Motion tokens: <N tokens added> | <N migrations> | <N TODOs left>
Phase 6 — Focus audit: <N components patched> | <N flagged for follow-up>
Total tests: <before> → <after>
Commits: <N>
If any phase failed verification, surface that — do not silently skip it.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.