- Home
- Skills
- Frontend & Web Apps
- UI Component Spec — Design to Tailwind Handoff
UI Component Spec — Design to Tailwind Handoff
Turn a component idea into a build-ready spec: props, variants, every state, tokens, the a11y contract, and the code.
$9
Works with the AI tools you already use
UI Component Spec — Design to Tailwind Handoff
Example session with this skill installed
Spec out a Toast/notification component for our React app. We use Tailwind and Radix, and we have semantic tokens already. Needs success, error and warning kinds, an optional action button, and it should stack when there are several.
- Read your context and instructions
- Compiled the ui component spec
- Generated the app files
Toast
Status: draft · Built on: Radix @radix-ui/react-toast · Owner: Design Systems
Purpose
A short, transient message confirming something happened or reporting that it didn't.
Use when: the result isn't already visible on screen — a background job finished, a save failed, an item was deleted with an undo available.
Don't use when: the user must act before continuing → use Dialog. The message belongs to one field → use inline error text. The state is permanent → use Banner.
Anatomy
┌─ Root ──────────────────────────────────────────┐
│ ┌Icon┐ ┌─ Title ──────────────┐ ┌─ Close ─┐ │
│ └────┘ │ Description │ └─────────┘ │
│ └──────────────────────┘ ┌─ Action ─┐ │
└───────────────────────────────────└──────────┘─┘
| Part | Required | Notes |
|---|---|---|
| Root | ✅ | Radix Toast.Root; receives className and ...rest |
| Icon | — | Derived from kind, 20px, aria-hidden="true" |
| Title | ✅ | Radix Toast.Title, 1 line, truncates |
| Description | — | Radix Toast.Description, max 2 lines |
| Action | — | Radix Toast.Action, requires altText |
| Close | ✅ | Radix Toast.Close, icon button |
| Viewport | ✅ | One per app, rendered at root |
Props
| Prop | Type | Default | Req | Description |
|---|---|---|---|---|
kind | 'success' | 'error' | 'warning' | 'info' | 'info' | — | Sets icon, accent and live-region politeness |
title | string | — | ✅ | One line, ≤ 60 chars |
description | string | — | — | ≤ 140 chars |
action | { label: string; onClick: () => void; altText: string } | — | — | altText is required by Radix for screen readers |
duration | number | null | 5000 | — | null = never auto-dismiss |
onOpenChange | (open: boolean) => void | — | — | |
className | string | — | — | Merged via twMerge onto Root |
Controlled/uncontrolled: uncontrolled by default via the useToast() hook; open + onOpenChange for controlled use.
Variants
| success | error | warning | info | |
|---|---|---|---|---|
| Icon | CheckCircle | AlertCircle | AlertTriangle | Info |
| Accent (left border 3px) | --color-success | --color-danger | --color-warning | --color-info |
Default duration | 5000 | null | 8000 | 5000 |
role | status | alert | alert | status |
DECISION NEEDED: should error auto-dismiss? Recommend
no (duration: null) — an error the user didn't see is an error that gets reported as a bug. Overridable per call.
States
| State | Background | Border | Other |
|---|---|---|---|
| default | --color-surface-raised | 1px --color-border, 3px left accent | shadow --shadow-md |
| hover | --color-surface-raised | same | timer pauses (Radix default) |
| focus-within | same | same | timer pauses; Close and Action show their own rings |
| swiping | same | same | follows pointer X, 0.9 opacity |
| stacked (2nd+) | same | same | see Stacking |
Close / Action focus: focus-visible:ring-2 ring-focus ring-offset-2 ring-offset-surface-raised
Sizing
| Token | Value |
|---|---|
| min-width / max-width | 320px / 440px |
| padding | 16px |
| gap icon → text | 12px |
| gap title → description | 2px |
| radius | --radius-md (6px) |
| icon | 20px |
| close target | 24×24 (meets WCAG 2.2 SC 2.5.8) |
| viewport offset | 16px from edge; 24px ≥ 768px |
Typography
Title 14px / 600 / 1.4 · Description 14px / 400 / 1.5, --color-text-muted · Action 14px / 600
Title truncates at 1 line; description clamps at 2 (line-clamp-2).
Stacking
- Max 3 visible; older ones collapse behind with
translateY(-8px) scale(0.97)per level. - New toasts enter at the front. Queue beyond 3 — don't drop.
- Hovering the stack expands it to full height.
Toast.Viewportis the single source of stacking order — don't manage it per toast.
Accessibility
Element: Radix Toast.Root → renders <li> inside the viewport's <ol>.
Live region: the viewport is the live region and must be in the DOM before any toast is inserted. role="status" for success/info (polite), role="alert" for error/warning (assertive).
| Key | Action |
|---|---|
| F8 | Focus the toast viewport (Radix hotkey) |
| Tab | Move through Action, then Close |
| Escape | Dismiss focused toast |
Focus: toasts never steal focus. Focus returns to where it was on dismiss.
Announcements: action.altText is what a screen reader hears — write it as an instruction ("Press F8 then Enter to undo"), not a repeat of the label.
Reduced motion: slide-in and swipe become a 120ms opacity fade; stacking offset applies instantly with no transition.
Behaviour
- Timer pauses on hover, focus-within, and when the tab is backgrounded.
- Identical consecutive toasts collapse into one with a count badge rather than stacking duplicates.
- Action click dismisses the toast unless
onClickreturnsfalse. - Swipe right dismisses on touch; 80px threshold.
Responsive
| Breakpoint | Change |
|---|---|
| < 640px | Full width minus 32px, anchored bottom, swipe down to dismiss |
| ≥ 640px | 320–440px, anchored bottom-right, swipe right |
Implementation
const toast = cva(
'pointer-events-auto relative flex w-full items-start gap-3 rounded-md border ' +
'border-l-[3px] bg-surface-raised p-4 shadow-md ' +
'data-[state=open]:animate-in data-[state=open]:slide-in-from-bottom-2 ' +
'data-[state=closed]:animate-out data-[state=closed]:fade-out-80 ' +
'data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] ' +
'motion-reduce:animate-none motion-reduce:transition-none',
{
variants: {
kind: {
success: 'border-l-success',
error: 'border-l-danger',
warning: 'border-l-warning',
info: 'border-l-info',
},
},
defaultVariants: { kind: 'info' },
}
)
const ICON = { success: CheckCircle, error: AlertCircle, warning: AlertTriangle, info: Info }
const POLITENESS = { success: 'status', error: 'alert', warning: 'alert', info: 'status' } as const
<Toast.Root className={cn(toast({ kind }), className)}
duration={duration} role={POLITENESS[kind]} {...rest}>
<Icon className="mt-0.5 size-5 shrink-0" aria-hidden="true" />
<div className="min-w-0 flex-1">
<Toast.Title className="truncate text-sm font-semibold">{title}</Toast.Title>
{description && (
<Toast.Description className="mt-0.5 line-clamp-2 text-sm text-muted">
{description}
</Toast.Description>
)}
</div>
{action && (
<Toast.Action altText={action.altText} asChild>
<button className="shrink-0 text-sm font-semibold text-primary
focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-focus focus-visible:ring-offset-2">
{action.label}
</button>
</Toast.Action>
)}
<Toast.Close className="shrink-0 rounded p-0.5 text-muted hover:text-text
focus-visible:outline-none focus-visible:ring-2
focus-visible:ring-focus" aria-label="Dismiss">
<X className="size-4" />
</Toast.Close>
</Toast.Root>
Test cases
- Each
kindrenders the right icon, accent androle - Error toast does not auto-dismiss
- Timer pauses on hover and on focus-within
- F8 → Tab → Enter fires the action
- Escape dismisses the focused toast only
- 4th toast queues rather than dropping
- 200-character description clamps to 2 lines, no overflow
-
prefers-reduced-motionremoves slide and swipe animation - Contrast passes for all four accents in light and dark
- Close button target ≥ 24×24
Open decisions
DECISION NEEDED:error auto-dismiss — recommendnull(see Variants).DECISION NEEDED:do we need aloadingtoast for long jobs? Recommend no — use an inline progress indicator; a toast that lives for 40s isn't a toast.
ui-component-spec-design-to-tailwind-han-app.zip
ZIP · project files
Example file from a real run - the skill writes it into your workspace.
Connects securely to your tools. The creator never sees your data.
What you get
About this skill
The problem
"Here's the design" is where most handoffs end, and where the six-question Slack thread begins. What are the props? What happens on hover? What about disabled and loading at the same time? What's the keyboard behaviour? Half of it gets guessed, and the guesses diverge across the codebase.
What it does
- Produces a full component spec: purpose and when not to use it, named anatomy, a real props table, a variant by size grid with invalid combinations marked, every state with its exact token, sizing, typography and responsive behaviour.
- Writes the accessibility contract — correct element, keyboard table, focus management, ARIA, reduced-motion fallback — using the WAI-ARIA patterns for the twelve components this comes up for most.
- Gives real implementation code: cva variants, twMerge overrides, semantic tokens instead of hex values, focus-visible instead of focus.
- Flags genuinely open questions as DECISION NEEDED with a recommendation, rather than silently picking.
Why this beats prompting it yourself
A spec is only useful if it's complete, and completeness is exactly what a freeform answer misses. This enforces the sections that get skipped — the invalid variant combinations, the disabled-and-loading interaction, where focus goes on close. It also extends what you already use rather than reinventing it: if you're on Radix or shadcn, it specs the visual layer and says which primitive handles the rest.
Use cases
- Adding a component to a design system and wanting it documented once, properly
- Handing a design to an engineer who isn't in the room
- Building a component yourself and wanting the states enumerated before you start
- Reviewing an existing component for what's missing
Known limitations
Tailwind and React are the default examples; other stacks are supported but get less idiomatic code. Doesn't generate visual designs — it specs decisions already made.
How to install
Works the same in every agent - Claude, Cursor, Codex, Copilot and 20+ more.
- 1
Download the ZIP
Free skills download straight away. Paid skills unlock right after purchase.
- 2
Unzip into your skills folder
Every agent reads skills from one folder on your machine. Drop the unzipped folder in there.
- 3
Ask your agent to use it
Restart the agent if it was already running. It picks the skill up automatically - no config needed.
Skills folder by agent
Click the path to copy it. Create the folder if it does not exist yet.
Reviews
No reviews yet
Be one of the first to try it. Every listed skill passes our trust checks below.
Security scanned
Passed our 8-point scan before listing
Fresh listing
Recently published to Agensi
30-day refund
Not a fit? Get your money back
Trust & safety
Security scanned
Verified clean 7 days ago
- Passed all security checks, Safe to install