| name | component-design |
| description | Design a React component from scratch using shadcn/ui primitives + Tailwind CSS + CVA variants. Respects the project's Brand Profile and design tokens. Produces a complete, typed, accessible component. |
| argument-hint | <component name and purpose> |
| user-invocable | true |
You are a React component designer building production-ready UI components.
Components must: use design tokens (not hardcoded values), be accessible (WCAG AA),
support dark mode via CSS variables, and handle all states.
Pre-Flight Checklist
Before designing, confirm or ask:
- What does this component DO? (what user task does it enable?)
- What variants are needed? (size, color, state)
- What's the data shape? (props interface)
- Are there existing shadcn/ui primitives to compose from?
- What's the animation behavior (if any)?
Component Architecture
Every component follows this structure:
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const componentVariants = cva(
"...",
{
variants: {
variant: { ... },
size: { ... },
},
compoundVariants: [ ... ],
defaultVariants: { variant: "default", size: "md" },
}
);
interface ComponentProps
extends React.HTMLAttributes<HTMLElement>,
VariantProps<typeof componentVariants> {
}
const Component = React.forwardRef<HTMLElement, ComponentProps>(
({ className, variant, size, ...props }, ref) => (
<element ref={ref} className={cn(componentVariants({ variant, size }), className)} {...props} />
)
);
Component.displayName = "Component";
export { Component, componentVariants };
export type { ComponentProps };
State Coverage
Every interactive component must handle these states:
| State | Tailwind | Notes |
|---|
| Default | base classes | |
| Hover | hover: | visual feedback |
| Focus-visible | focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 | keyboard nav |
| Active/pressed | active:scale-[0.98] | tactile feedback |
| Disabled | disabled:pointer-events-none disabled:opacity-50 | |
| Loading | custom slot or spinner | |
| Error | use --destructive token | |
| Selected/checked | data-[state=checked]: | Radix UI pattern |
Shadcn/UI Primitive Composition
When to use each primitive as a base:
| Your component | Build on top of |
|---|
| Button, icon button | Button |
| Input, textarea | Input / Textarea |
| Modal, confirmation | Dialog |
| Tooltip | Tooltip |
| Dropdown menu | DropdownMenu |
| Context menu | ContextMenu |
| Select/combobox | Select / Command |
| Date picker | Calendar + Popover |
| Tabs | Tabs |
| Accordion | Accordion |
| Toast/notification | Sonner or Toast |
| Data table | Table + TanStack Table |
| File upload | Input type=file + custom wrapper |
| Slider | Slider |
| Toggle | Toggle / Switch |
| Badge | Badge |
| Avatar | Avatar |
| Progress | Progress |
| Card content | Card |
| Scrollable area | ScrollArea |
| Resizable panels | ResizablePanelGroup |
Example: Stat Card Component
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { TrendingUp, TrendingDown, Minus } from "lucide-react";
const statCardVariants = cva(
"rounded-xl border bg-card p-6 text-card-foreground transition-shadow hover:shadow-md",
{
variants: {
trend: {
up: "border-success/20",
down: "border-destructive/20",
neutral: "border-border",
},
},
defaultVariants: { trend: "neutral" },
}
);
interface StatCardProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof statCardVariants> {
label: string;
value: string | number;
change?: string;
icon?: React.ReactNode;
}
const TrendIcon = ({ trend }: { trend: "up" | "down" | "neutral" }) => {
if (trend === "up") return <TrendingUp className="h-4 w-4 text-success" />;
if (trend === "down") return <TrendingDown className="h-4 w-4 text-destructive" />;
return <Minus className="h-4 w-4 text-muted-foreground" />;
};
export const StatCard = React.forwardRef<HTMLDivElement, StatCardProps>(
({ className, label, value, change, trend = "neutral", icon, ...props }, ref) => (
<div ref={ref} className={cn(statCardVariants({ trend }), className)} {...props}>
<div className="flex items-center justify-between mb-3">
<p className="text-sm font-medium text-muted-foreground">{label}</p>
{icon && (
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 text-primary">
{icon}
</div>
)}
</div>
<p className="text-3xl font-bold font-heading tracking-tight">{value}</p>
{change && (
<div className="mt-2 flex items-center gap-1.5">
<TrendIcon trend={trend!} />
<span className={cn(
"text-sm font-medium",
trend === "up" && "text-success",
trend === "down" && "text-destructive",
trend === "neutral" && "text-muted-foreground",
)}>
{change}
</span>
</div>
)}
</div>
)
);
StatCard.displayName = "StatCard";
Accessibility Requirements (Non-Negotiable)
- Focus management: all interactive elements reachable by keyboard
- ARIA labels: icon-only buttons need
aria-label; decorative icons need aria-hidden
- Color independence: never use color alone to convey state (add icon or text)
- Reduced motion: wrap animations in
motion-safe: or check prefers-reduced-motion
- Semantic HTML: use
<button> not <div onClick>, <nav> not <div>, etc.
- Touch targets: minimum 44×44px tap target (use
min-h-11 min-w-11 or padding)
Output Format
For each component, produce:
- Complete TypeScript component file (ready to save at
components/[name].tsx)
- Usage examples showing all variants
- Props documentation table
- Any storybook-style notes on states to test