.claude/skills/figma-to-tailwind/SKILL.md
Implement UI components from Figma designs using semantic HTML, Tailwind 4, and tailwind-variants (tv()) following project conventions
npx skillsauth add guidodinello/claude-dotfiles figma-to-tailwindInstall 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.
Reference guide for translating Figma designs into production-ready components with semantic HTML, Tailwind 4, and tailwind-variants.
| Element | Use when |
|---|---|
| <article> | Self-contained, independently distributable content (cards, posts, products) |
| <section> | Thematic grouping with its own heading — not for layout grouping |
| <div> | Pure layout grouping with no semantic meaning |
| <button> | Actions that don't navigate (modals, sidebars, triggering logic) |
| <a> | Navigation to a URL — always include href |
<section> just to group layout rows inside a card, use <div> instead<h1> inside a repeated card — only one <h1> per page. Use <h3> or lower inside cards; when in doubt, use <p> with text stylesflex-1 vs justify-betweenRow with one growing element + one fixed element (title + icon button):
flex-1 on the growing element — targeted, survives adding a third childjustify-between on the container — spreads all children, breaks with 3+ elementsAlways pair both utilities:
content: "flex flex-1 min-w-0 flex-col ..."
flex-1 — fills remaining horizontal spacemin-w-0 — allows shrinking below natural content width, preventing overflowSet items-start on the row container — items-stretch (default) distorts the image wrapper when content is taller:
body: "flex flex-row gap-3 items-start"
Always constrain images via their wrapper, not the <img> tag:
<div className="w-[120px] shrink-0 aspect-[10/13] overflow-hidden rounded-lg">
<img className="w-full h-full object-cover" />
</div>
w-* + aspect-* on the wrappershrink-0 prevents compression in flex rowsoverflow-hidden + rounded-* on the wrapper clips to border radiusobject-cover on <img> fills the box without distortionwidth / height → 120 × 156 → aspect-[10/13]Prefer native Tailwind 4 scale values over arbitrary ones.
| Figma token | px | Tailwind |
|---|---|---|
| Space/1 | 4px | gap-1 / p-1 |
| Space/2 | 8px | gap-2 / p-2 |
| Space/3 | 12px | gap-3 / p-3 |
| Space/4 | 16px | gap-4 / p-4 |
| Figma token | Tailwind |
|---|---|
| Font Styles/SM | text-sm |
| Font Styles/Base | text-base |
| Font Styles/LG | text-lg |
| Font Styles/XL | text-xl |
| Font Weight/Regular | font-normal |
| Font Weight/Medium | font-medium |
| Font Weight/Bold | font-bold |
Figma commonly specifies different font weights per interaction state (e.g. Regular for default, Medium for hover/active). Don't put font-medium in the layout/size variant — it belongs in the interaction state variant:
// ❌ hardcodes font-medium for all states
collapsed: {
false: "w-full h-12 gap-2 px-3 text-base font-medium justify-start",
},
// ✅ weight lives in the state variant
collapsed: {
false: "w-full h-12 gap-2 px-3 text-base justify-start",
},
active: {
true: "font-medium ...",
false: "font-normal ... hover:font-medium",
},
Check the Figma spec for each state (default / hover / selected / disabled) independently — don't assume weight is constant across states.
line-height — only add leading-none when Figma explicitly says 100%; otherwise leave as defaultletter-spacing: 0.5% — negligible, skip unless visually significantleading-trim: NONE — ignoreopacity: 1 — ignore (default)angle: 0deg — ignoreCheck app.css first. If the project defines tokens in a @theme block, Tailwind 4 generates utility classes directly — do not use the (--var) escape hatch for registered tokens:
| Token in @theme | Correct utility | ❌ Wrong |
|---|---|---|
| --color-text-brand-default | text-text-brand-default | text-(--color-text-brand-default) |
| --color-background-brand-tertiary | bg-background-brand-tertiary | bg-(--color-background-brand-tertiary) |
| --shadow-sidebar | shadow-sidebar | shadow-(--shadow-sidebar) |
Rule: strip the type prefix (--color-, --shadow-, etc.) — what remains is the Tailwind utility suffix.
tailwind-variants (tv())Once using tv(), every style lives in a slot. Never mix raw className strings with slot calls on the same component:
// ❌ mixed
<div className={styles.content()}>
<h3 className="flex-1 text-lg font-medium text-neutral-900">...</h3>
</div>
// ✅ everything in slots
<h3 className={styles.title()}>...</h3>
If a slot is defined in tv() but never referenced in JSX, delete it.
| ❌ Visual name | ✅ Semantic name |
|---|---|
| infoLink | infoButton (it's a <button>, not an <a>) |
| bigText | price |
| greyLabel | biomarkers |
Tailwind breakpoints are viewport-based, but content lives inside a layout with a sidebar (md:pl-[280px]) and section padding (md:px-14 = 56px each side) — subtracting ~392px from the viewport.
| Viewport | Sidebar | Padding | Available | 2 cols each |
|---|---|---|---|---|
| lg 1024px | 280px | 112px | 632px | ~308px ← too tight |
| xl 1280px | 280px | 112px | 888px | ~436px ← comfortable |
Prefer xl:grid-cols-2 over lg:grid-cols-2 in sidebar layouts.
Formula: column width = (viewport − sidebar − section padding − gaps) / columns
sm+body: "flex flex-col gap-3 sm:flex-row sm:items-start",
imageWrapper: "aspect-video w-full shrink-0 overflow-hidden rounded-lg sm:aspect-[10/13] sm:w-[120px]",
aspect-video banner on top, text below at 100% widthsm+: 120px portrait image left, text right — matches Figma designNever use flex-row with a fixed-width image as the default — on a 344px screen with 120px image + 32px padding, text collapses to ~148px.
leading-none is only safe for single-line textleading-none sets line-height: 1. Only use on guaranteed single-line text (price, badge, short label). For titles, descriptions, or any copy that may wrap, use leading-snug or leading-normal:
// ❌ breaks on long titles
title: "text-lg font-medium leading-none",
// ✅
title: "text-lg font-medium leading-snug",
items-start in rows with multi-line contentWhen a flex row has a potentially multi-line element (title, description) next to a short inline element (button, badge, icon), use items-start — not items-center:
// ❌ button jumps to vertical center of a 4-line title
titleRow: "flex items-center gap-2",
// ✅ button pins to the top
titleRow: "flex items-start gap-2",
shrink-0 on fixed inline elementsAny small, fixed-size element in a flex row next to a flex-1 sibling must have shrink-0:
// Applies to: icon buttons, status badges, avatars, price labels, "Info ›" links
infoButton: "flex shrink-0 items-center gap-1 text-sm font-medium",
leading-snug or leading-normal, never leading-noneflex-1 text has shrink-0items-start, not items-centerxl: not lg: in sidebar layouts)tv() slots — no mixed raw className stringstv() definition<img> tag directlyapp.css when available@theme tokens used as direct utility classes (bg-background-brand-tertiary), not as arbitrary CSS vars (bg-(--color-background-brand-tertiary))development
Writes React components without unnecessary useEffect. Use when creating/reviewing React components, refactoring effects, or when code uses useEffect to transform data or handle events.
development
Show a Claude Code usage report — model token breakdown, estimated costs, top projects, and session patterns. Delegates to the stats-analyzer subagent (Haiku) to avoid polluting the main context with raw data.
development
Use this skill whenever the user wants to write, refine, or break down a subtask for a software ticket — especially backend endpoints, frontend components, or API integrations. Trigger when the user shares a user story, acceptance criteria, or ticket scope and asks for a subtask, refinement card, or implementation breakdown. Also trigger when the user says things like "help me refine this", "write a subtask for X", "break this down", or "create a card for the endpoint / component / feature". This skill produces structured, audience-appropriate subtask write-ups for developers, PMs, and QAs alike.
development
Run the full quality pipeline (type-check, linting, tests) via the quality-checker subagent. Returns a concise summary of issues without flooding the main context with raw output.