shadcn Component Review
Systematic review process for ensuring custom components and layouts align with shadcn design patterns, official theme styles, and Radix UI best practices.
Quick Start
Planning Phase (Preventive)
Before building a component:
- Check existing patterns: Review similar components in
src/ui/andsrc/components/ - Reference your theme style: See references/theme-styles.md for spacing/shape patterns
- Use shadcn MCP (if available): Query components via shadcn MCP server
- Review checklist: Use references/review-checklist.md as planning guide
Review Phase (Post-Build)
After building a component:
- Run spacing audit: Check against your theme's spacing patterns
- Verify structure: Ensure proper use of
data-slotattributes and composition - Check design tokens: Verify semantic tokens only (no hardcoded colors)
- Test composability: Ensure component can be reused and customized via props
- Validate responsive: Test mobile-first approach and breakpoints
Core Review Areas
1. Spacing (Theme-Dependent)
Use gap-* for flex/grid containers. Spacing varies by theme style:
| Theme | Spacing | Shape |
|---|---|---|
| Vega | Standard | Classic shadcn |
| Nova | Compact | Reduced padding/margins |
| Maia | Generous | Soft, rounded |
| Lyra | Standard | Boxy, sharp |
| Mira | Dense | Compact interfaces |
See references/theme-styles.md for theme-specific patterns.
2. Component Structure
- Use
data-slotattributes:data-slot="component-name" - Sub-components:
ComponentName.Header,ComponentName.Content - Composition over modification (never edit
src/ui/*directly)
3. Design Tokens
Semantic tokens only - never hardcoded colors:
// ✅ text-muted-foreground, bg-muted, hover:bg-accent
// ❌ text-neutral-500, bg-gray-100, hover:bg-neutral-50
4. Composability
- Prop-based customization (variants, sizes)
- Slot-based composition (children, content blocks)
- Single responsibility, clear interface
5. Responsive Design
- Mobile-first (< 768px base)
- Breakpoints:
md:(768px+),lg:(1024px+) - Touch targets: min 44px
- Flex children:
min-w-0to prevent overflow
See references/review-checklist.md for detailed checklists.
Foundational Patterns
CVA (Class Variance Authority)
shadcn components use CVA for type-safe, declarative variants:
import { 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> {}
Key points:
- Base styles in first argument (always applied)
- Variants as declarative object
- Type-safe props via
VariantProps - Extend with new variants, don't modify base
cn() Utility
Always use cn() for conditional and override classes:
import { cn } from "@/lib/utils"
// Combines clsx (conditionals) + tailwind-merge (conflict resolution)
<div className={cn(
"base-classes",
isActive && "active-classes",
className // allows consumer overrides
)} />
Why it matters:
- Prevents CSS cascade conflicts
- Enables prop-based class overrides
- Handles conditional classes cleanly
Theme-Aware Styling
shadcn themes use CSS variables for consistent styling across components.
Border radius is theme-defined via --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]">
Theme CSS variables (defined in your theme's CSS):
--radius- Base radius unit--background,--foreground- Base colors--primary,--secondary,--accent- Semantic colors--muted,--card,--popover- Surface colors
Custom theme extensions: If your project needs theme-switchable radius (e.g., pill vs sharp), create utility classes mapped to CSS variables:
/* Example: Theme-switchable radius */
.rounded-theme-button {
border-radius: var(--radius-button);
}
Animation Patterns
Use consistent, subtle animations. See references/animation-patterns.md for:
- Timing standards (150ms fast, 200ms normal, 300ms slow)
- Easing curves (ease-out for enter, ease-in for exit)
- Radix
data-stateanimation patterns - Framer Motion patterns for enter/exit
- Accessibility (
motion-safe:prefixes)
Resources
shadcn Documentation
- Components: https://ui.shadcn.com/docs/components
- Blocks: https://ui.shadcn.com/blocks
- Themes: https://ui.shadcn.com/themes
- Theme Creator: https://ui.shadcn.com/create
2025 Updates
Visual Styles (via npx shadcn create):
- Vega - Classic shadcn/ui look
- Nova - Compact layouts, reduced spacing
- Maia - Soft and rounded, generous spacing
- Lyra - Boxy and sharp, pairs with mono fonts
- Mira - Dense interfaces
New utility components:
input-group/button-group- Grouped controlsempty- Empty state patternsfield- Form field wrapperspinner- Loading indicator
Technical updates:
- Full Tailwind v4 support (
@themedirective) - OKLCH colors (from HSL)
- React 19 compatibility
Community Tools
- tweakcn: https://tweakcn.com/ - Interactive theme editor
- Shadcn Studio: https://shadcnstudio.com/ - Theme generator
- Shadcn Themer: https://shadcnthemer.com/ - Theme creator
Review Workflow
Step 1: Structure Review
Check component structure against shadcn patterns:
// ✅ 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>
Step 2: Spacing Audit
Verify spacing follows your theme's patterns:
- Check all flex containers use
gap-*notspace-y-*or margins - Verify spacing values follow Tailwind scale (2, 4, 6, 8, etc.)
- Ensure responsive spacing uses
gap-X md:gap-Ypattern - Match spacing density to your theme (Maia=generous, Nova/Mira=compact)
Step 3: Design Token Check
Verify semantic tokens only:
# Check for hardcoded colors
grep -r "neutral-\|gray-\|slate-" [component-file]
Step 4: Composability Review
Ensure component can be:
- Reused in other contexts
- Customized via props (variants, sizes)
- Composed with other components
- Extended without modification
Step 5: Responsive Verification
Test at breakpoints:
- Mobile: 375px
- Tablet: 768px
- Desktop: 1280px
Examples
Good Component Pattern
// ✅ 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>
);
}
Bad Component Pattern
// ❌ 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>
);
}
Issues:
- Uses
space-y-*instead ofgap-* - Hardcoded colors (
text-gray-900,text-neutral-500) - Missing
data-slotattributes - Inconsistent spacing (
mb-4,mt-2instead of flex gap)
Additional Resources
- Theme Styles: references/theme-styles.md
- Review Checklist: references/review-checklist.md
- Animation Patterns: references/animation-patterns.md
- V2 Enhancement Ideas: references/v2-notes.md