Loading...
Loading...
Review custom components and layouts against shadcn design patterns, theme styles (Maia, Vega, Lyra, Nova, Mira), component structure, composability, and Radix UI best practices. Use when planning new components, reviewing existing components, auditing spacing, checking component structure, or verifying shadcn best practices alignment.
npx skill4agent add mattbx/shadcn-skills shadcn-component-reviewsrc/ui/src/components/data-slotgap-*| Theme | Spacing | Shape |
|---|---|---|
| Vega | Standard | Classic shadcn |
| Nova | Compact | Reduced padding/margins |
| Maia | Generous | Soft, rounded |
| Lyra | Standard | Boxy, sharp |
| Mira | Dense | Compact interfaces |
data-slotdata-slot="component-name"ComponentName.HeaderComponentName.Contentsrc/ui/*// ✅ text-muted-foreground, bg-muted, hover:bg-accent
// ❌ text-neutral-500, bg-gray-100, hover:bg-neutral-50md:lg:min-w-0import { cva, type VariantProps } from "class-variance-authority"
const buttonVariants = cva(
"inline-flex items-center justify-center font-medium transition-colors",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
outline: "border border-input bg-background hover:bg-accent",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 px-3 text-sm",
},
},
defaultVariants: { variant: "default", size: "default" },
}
)
interface ButtonProps extends VariantProps<typeof buttonVariants> {}VariantPropscn()import { cn } from "@/lib/utils"
// Combines clsx (conditionals) + tailwind-merge (conflict resolution)
<div className={cn(
"base-classes",
isActive && "active-classes",
className // allows consumer overrides
)} />--radius// ✅ Uses theme radius (adapts to Maia rounded vs Lyra sharp)
<Button className="rounded-md"> // Uses --radius variable
<Card className="rounded-lg">
// ❌ Hardcoded (ignores theme settings)
<Button className="rounded-[20px]">--radius--background--foreground--primary--secondary--accent--muted--card--popover/* Example: Theme-switchable radius */
.rounded-theme-button {
border-radius: var(--radius-button);
}data-statemotion-safe:npx shadcn createinput-groupbutton-groupemptyfieldspinner@theme// ✅ Good: Proper structure with data-slot
<div data-slot="component-name" className="flex flex-col gap-4">
<div data-slot="component-header" className="flex flex-col gap-2">
{/* Header content */}
</div>
<div data-slot="component-content">
{/* Main content */}
</div>
</div>
// ❌ Bad: Missing data-slot, inconsistent spacing
<div className="space-y-4">
<div className="mb-2">
{/* Header content */}
</div>
<div>
{/* Main content */}
</div>
</div>gap-*space-y-*gap-X md:gap-Y# Check for hardcoded colors
grep -r "neutral-\|gray-\|slate-" [component-file]// ✅ Follows shadcn patterns
export function PageContent({
heading,
description,
contentBlock,
children,
}: PageContentProps) {
return (
<div
data-slot="page-content"
className="min-w-0 flex flex-col gap-4 md:gap-6"
>
<div data-slot="page-content-header" className="flex flex-col gap-2">
<h1 className="text-xl md:text-2xl tracking-tight font-semibold text-foreground">
{heading}
</h1>
{description && (
<p className="text-sm text-muted-foreground">{description}</p>
)}
</div>
{contentBlock && (
<div data-slot="page-content-block">{contentBlock}</div>
)}
{children}
</div>
);
}// ❌ Violates multiple patterns
export function PageContent({ heading, description }: Props) {
return (
<div className="space-y-6">
<div className="mb-4">
<h1 className="text-2xl font-bold text-gray-900">{heading}</h1>
<p className="mt-2 text-sm text-neutral-500">{description}</p>
</div>
</div>
);
}space-y-*gap-*text-gray-900text-neutral-500data-slotmb-4mt-2