Loading...
Loading...
Build new Next.js applications or migrate existing frontends (React, Vue, Angular, vanilla JS, etc.) to Next.js + shadcn/ui with systematic analysis and conversion. Enforces shadcn design principles - CSS variables for theming, standard UI components, no hardcoded values, consistent typography/colors. Use for creating Next.js apps, migrating frontends, adopting shadcn/ui, or standardizing component libraries. Includes MCP integration for shadcn documentation and automated codebase analysis.
npx skill4agent add ovachiever/droid-tings nextjs-shadcn-builderUser Request
├─ Creating New Next.js App
│ └─ Follow "Creating New Application" workflow (Phase 3 onwards)
│
└─ Migrating Existing Codebase
├─ Phase 1: Codebase Analysis
├─ Phase 2: Migration Planning
├─ Phase 3: Next.js + shadcn Setup
├─ Phase 4: Systematic Conversion
└─ Phase 5: Verification & Cleanuppython ./scripts/analyze-codebase.py /path/to/existing/codebasecodebase-analysis.jsoncomponent-inventory.json{
"components": [
{
"name": "UserCard",
"path": "src/components/UserCard.tsx",
"type": "functional",
"complexity": "simple",
"shadcn_equivalent": "Card",
"hardcoded_values": ["#3b82f6", "16px padding"],
"dependencies": ["react", "styled-components"]
}
]
}bash ./scripts/detect-hardcoded-values.sh /path/to/existing/codebase#hexrgb()rgba()hsl()margin: 20pxpadding: 1remstyle={{...}}hardcoded-values-report.mdpython ./scripts/generate-migration-report.pymigration-analysis-report.md# Frontend Migration Analysis Report
## Executive Summary
[One-paragraph overview: framework, size, complexity]
## Current State Analysis
- **Framework**: React 18.2.0
- **Build Tool**: Vite 4.3.0
- **Component Count**: 47 components
- **Styling**: styled-components + custom CSS
- **State Management**: Redux Toolkit
- **Routing**: React Router v6
## Hardcoded Values Detected
- Colors: 142 instances across 34 files
- Spacing: 89 instances across 28 files
- Custom fonts: 3 non-standard fonts
- Inline styles: 67 instances
## Component Categorization
- **Simple (shadcn mapping exists)**: 28 components
- **Moderate (requires adaptation)**: 13 components
- **Complex (custom development needed)**: 6 components
## Recommended Migration Plan
1. Phase 3: Setup Next.js + shadcn infrastructure
2. Phase 4.1: Convert layout components (Header, Footer, Layout)
3. Phase 4.2: Convert simple UI (Button, Card, Badge → shadcn equivalents)
4. Phase 4.3: Convert forms (Input, Select → shadcn/ui Form components)
5. Phase 4.4: Convert complex components (DataTable, Charts)
6. Phase 4.5: Styling standardization (CSS variables)
7. Phase 4.6: Pages and routing
8. Phase 5: Verification and cleanup
## Estimated Effort
- **Total Components**: 47
- **Batches**: 9-10 batches
- **Complexity**: Moderatecomponent-inventory.json# Check if MCP server is available
# Try accessing https://ui.shadcn.com/docs/mcpnpx shadcn@latest mcp init --client claude| Existing Component | shadcn Equivalent | Complexity | Priority | Notes |
|---|---|---|---|---|
| CustomButton | Button | Low | 1 | Props mostly compatible |
| Modal | Dialog | Medium | 2 | Different API, uses Radix |
| DataTable | Table + DataTable | High | 3 | Requires custom hooks |
| Dropdown | DropdownMenu | Low | 1 | Direct mapping |
| DatePicker | Calendar + Popover | Medium | 2 | Composition pattern |
./references/react-to-nextjs.md./references/vue-to-nextjs.md./references/angular-to-nextjs.md./references/styling-migration.mdmigration-plan.md# Next.js + shadcn Migration Plan
## Project: [Project Name]
## Date: [Current Date]
## Estimated Timeline: [X batches]
## Migration Strategy
### Approach
- Incremental migration with parallel running old and new code
- Batch-based conversion (5-10 components per batch)
- Test after each batch before proceeding
- Feature flag new components during transition
### Success Criteria
- All components use shadcn/ui or shadcn patterns
- Zero hardcoded colors/spacing (CSS variables only)
- 100% TypeScript coverage
- Passing test suite
- Lighthouse score >= 90
- No accessibility violations
## Detailed Batch Plan
[Include all batches from 2.2 with specific components listed]
## Timeline
Batch 1: Layout & Structure (Days 1-2)
Batch 2: Simple UI (Days 3-4)
[etc.]
## Notes and Considerations
[Any special requirements, blockers, or dependencies]https://ui.shadcn.com/docs/mcpnpx shadcn@latest mcp init --client claudebash ./scripts/init-nextjs-shadcn.sh [project-name]# Check Node.js version (18+ required)
node -v
# Create Next.js project with App Router
npx create-next-app@latest [project-name] \
--typescript \
--tailwind \
--app \
--src-dir \
--import-alias "@/*" \
--no-turbopack
cd [project-name]# Initialize shadcn/ui
npx shadcn@latest init
# Configuration prompts:
# - TypeScript: Yes
# - Style: Default
# - Base color: Choose from slate/gray/zinc/neutral/stone
# - CSS variables: Yes (CRITICAL - required for theming)
# - Import alias: @/componentscomponents.jsonlib/utils.tstailwind.config.tsapp/globals.cssapp/globals.css@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--radius: 0.65rem;
/* Background - Pure white */
--background: 1 0 0;
--foreground: 0.141 0.005 285.823;
/* Card */
--card: 1 0 0;
--card-foreground: 0.141 0.005 285.823;
/* Popover */
--popover: 1 0 0;
--popover-foreground: 0.141 0.005 285.823;
/* Primary - Warm orange */
--primary: 0.646 0.222 41.116;
--primary-foreground: 0.98 0.016 73.684;
/* Secondary - Light purple-gray */
--secondary: 0.967 0.001 286.375;
--secondary-foreground: 0.21 0.006 285.885;
/* Muted - Subtle elements */
--muted: 0.967 0.001 286.375;
--muted-foreground: 0.552 0.016 285.938;
/* Accent */
--accent: 0.967 0.001 286.375;
--accent-foreground: 0.21 0.006 285.885;
/* Destructive - Red */
--destructive: 0.577 0.245 27.325;
/* Border and Input */
--border: 0.92 0.004 286.32;
--input: 0.92 0.004 286.32;
/* Focus ring */
--ring: 0.75 0.183 55.934;
/* Chart colors */
--chart-1: 0.837 0.128 66.29;
--chart-2: 0.705 0.213 47.604;
--chart-3: 0.646 0.222 41.116;
--chart-4: 0.553 0.195 38.402;
--chart-5: 0.47 0.157 37.304;
}
.dark {
/* Dark mode backgrounds */
--background: 0.141 0.005 285.823;
--foreground: 0.985 0 0;
/* Dark mode card */
--card: 0.21 0.006 285.885;
--card-foreground: 0.985 0 0;
/* Dark mode popover */
--popover: 0.21 0.006 285.885;
--popover-foreground: 0.985 0 0;
/* Dark mode primary - Brighter for contrast */
--primary: 0.705 0.213 47.604;
--primary-foreground: 0.98 0.016 73.684;
/* Dark mode secondary */
--secondary: 0.274 0.006 286.033;
--secondary-foreground: 0.985 0 0;
/* Dark mode muted */
--muted: 0.274 0.006 286.033;
--muted-foreground: 0.705 0.015 286.067;
/* Dark mode accent */
--accent: 0.274 0.006 286.033;
--accent-foreground: 0.985 0 0;
/* Dark mode destructive */
--destructive: 0.704 0.191 22.216;
/* Dark mode borders (with alpha) */
--border: 1 0 0 / 10%;
--input: 1 0 0 / 15%;
/* Dark mode focus ring */
--ring: 0.408 0.123 38.172;
/* Chart colors (consistent) */
--chart-1: 0.837 0.128 66.29;
--chart-2: 0.705 0.213 47.604;
--chart-3: 0.646 0.222 41.116;
--chart-4: 0.553 0.195 38.402;
--chart-5: 0.47 0.157 37.304;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}# Use the detection script to find existing colors
bash ./scripts/detect-hardcoded-values.sh /path/to/old/codebase
# Map old colors to new CSS variables (OKLCH format)
# Use https://oklch.com or https://colorjs.io to convert
# Example:
# Old: #FF6B35 (brand orange) → --primary: 0.646 0.222 41.116
# Old: #3B82F6 (blue) → --primary: 0.630 0.213 255.5
# Old: #10B981 (green) → --success: 0.710 0.180 165.4# Layout & Structure
npx shadcn@latest add card
npx shadcn@latest add separator
# Forms
npx shadcn@latest add button
npx shadcn@latest add input
npx shadcn@latest add label
npx shadcn@latest add select
npx shadcn@latest add checkbox
npx shadcn@latest add radio-group
npx shadcn@latest add form
# Navigation
npx shadcn@latest add navigation-menu
npx shadcn@latest add tabs
npx shadcn@latest add breadcrumb
# Feedback
npx shadcn@latest add alert
npx shadcn@latest add toast
npx shadcn@latest add dialog
npx shadcn@latest add tooltip
# Data Display
npx shadcn@latest add table
npx shadcn@latest add badge
npx shadcn@latest add avatar
# Overlays
npx shadcn@latest add popover
npx shadcn@latest add dropdown-menu
npx shadcn@latest add sheetnpm install next-themescomponents/theme-provider.tsx"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}app/layout.tsximport { ThemeProvider } from "@/components/theme-provider"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
)
}components/example-card.tsximport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
export function ExampleCard() {
return (
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>shadcn/ui Best Practices</CardTitle>
<CardDescription>
This card demonstrates proper shadcn patterns
</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
Notice: No hardcoded colors, using CSS variables via Tailwind classes,
standard shadcn components, and proper typography scale.
</p>
</CardContent>
<CardFooter className="flex justify-between">
<Button variant="outline">Cancel</Button>
<Button>Continue</Button>
</CardFooter>
</Card>
)
}text-muted-foregroundoutline// OLD: Custom button with hardcoded styles
const CustomButton = ({ children, onClick, variant = 'primary' }) => {
const styles = {
primary: {
backgroundColor: '#3b82f6', // HARDCODED!
color: '#ffffff',
padding: '8px 16px', // HARDCODED!
borderRadius: '6px'
},
secondary: {
backgroundColor: '#6b7280',
color: '#ffffff',
padding: '8px 16px',
borderRadius: '6px'
}
}
return (
<button style={styles[variant]} onClick={onClick}>
{children}
</button>
)
}
// NEW: shadcn Button with CSS variables
import { Button } from "@/components/ui/button"
const CustomButton = ({ children, onClick, variant = 'default' }) => {
return (
<Button variant={variant} onClick={onClick}>
{children}
</Button>
)
}// OLD: Hardcoded spacing and colors
<div style={{
backgroundColor: '#f3f4f6', // WRONG
padding: '20px', // WRONG
margin: '10px 0' // WRONG
}}>
// NEW: Tailwind classes using CSS variables
<div className="bg-secondary p-5 my-2.5">
// Or for custom spacing:
<div className="bg-secondary" style={{ padding: 'var(--spacing-5)' }}># Run tests
npm test
# Visual testing
npm run dev
# Manually verify component renders correctlybash ./scripts/detect-hardcoded-values.sh src/components/[batch-name]./references/shadcn-component-mapping.md| Pattern | Old Approach | shadcn Approach |
|---|---|---|
| Button | Custom styled button | |
| Modal/Dialog | Custom overlay | |
| Form Input | Custom input with validation | |
| Dropdown | Custom select | |
| Table | Custom table | |
| Tooltip | Custom hover component | |
| Toast/Notification | Custom notification | |
| Tabs | Custom tab component | |
| Card | Custom card | |
| Badge | Custom badge/pill | |
// Complex dashboard widget using shadcn primitives
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
export function DashboardWidget({ title, data, onRefresh }) {
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>{title}</CardTitle>
<Button variant="outline" size="sm" onClick={onRefresh}>
Refresh
</Button>
</CardHeader>
<CardContent>
{data.map(item => (
<div key={item.id} className="flex items-center justify-between py-2">
<span className="text-sm">{item.label}</span>
<Badge variant={item.status === 'success' ? 'default' : 'destructive'}>
{item.value}
</Badge>
</div>
))}
</CardContent>
</Card>
)
}// Custom component extending shadcn Button
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
interface IconButtonProps extends React.ComponentProps<typeof Button> {
icon: React.ReactNode
}
export function IconButton({ icon, children, className, ...props }: IconButtonProps) {
return (
<Button className={cn("flex items-center gap-2", className)} {...props}>
{icon}
{children}
</Button>
)
}npx shadcn@latest add [block-name]src/
pages/
Home.tsx
About.tsx
Dashboard.tsx
users/
UserList.tsx
UserDetail.tsxapp/
page.tsx # Home
about/
page.tsx # About
dashboard/
page.tsx # Dashboard
layout.tsx # Dashboard layout
users/
page.tsx # UserList
[id]/
page.tsx # UserDetail
layout.tsx # Root layout
loading.tsx # Loading state
error.tsx # Error boundary// OLD: React Router page
// src/pages/Dashboard.tsx
import { useNavigate } from 'react-router-dom'
export function Dashboard() {
const navigate = useNavigate()
return (
<div style={{ padding: '20px' }}>
<h1>Dashboard</h1>
<button onClick={() => navigate('/users')}>
View Users
</button>
</div>
)
}
// NEW: Next.js App Router page
// app/dashboard/page.tsx
import Link from 'next/link'
import { Button } from '@/components/ui/button'
export default function DashboardPage() {
return (
<div className="container py-6">
<h1 className="text-3xl font-bold mb-6">Dashboard</h1>
<Link href="/users">
<Button>View Users</Button>
</Link>
</div>
)
}<Link>./references/react-to-nextjs.md"use client"# Run all tests
npm test
# Run with coverage
npm test -- --coverage
# Ensure 100% of migrated components have passing tests./references/responsive-design-patterns.md# Test all Tailwind breakpoints
# sm: 640px, md: 768px, lg: 1024px, xl: 1280px, 2xl: 1536px
# Verify components respond correctly at each breakpoint./assets/component-templates/responsive-navigation.tsxresponsive-data-table.tsxresponsive-dashboard.tsxcomplex-form.tsxbash ./scripts/detect-hardcoded-values.sh src/./references/accessibility-best-practices.md# Install axe DevTools or use Lighthouse
npm install -D @axe-core/playwright
# Run accessibility tests
npm run test:a11y./references/accessibility-best-practices.md# Build for production
npm run build
# Analyze bundle
npm run analyze # If you have bundle analyzer configured# Remove old framework code
rm -rf src/old-components/ # or whatever old structure was
# Remove old dependencies
npm uninstall [old-framework] [old-ui-library] styled-components emotion ...
# Clean up old config files
rm -f .babelrc webpack.config.js # etc.migration-complete-report.md# Migration Completion Report
## Summary
Successfully migrated [Project Name] from [Old Framework] to Next.js + shadcn/ui.
## Statistics
- **Components Migrated**: 47
- **Lines of Code Changed**: ~5,200
- **Hardcoded Values Removed**: 231
- **CSS Variables Added**: 48
- **shadcn Components Used**: 18
## Test Results
- **Unit Tests**: 142/142 passing
- **Integration Tests**: 23/23 passing
- **Accessibility Score**: 98/100
- **Lighthouse Performance**: 94/100
## Before/After Comparison
### Before
- Framework: React 18 + Vite
- Styling: styled-components + custom CSS
- Hardcoded values: 231 violations
- Bundle size: 523 KB
- Lighthouse: 76
### After
- Framework: Next.js 15 + App Router
- Styling: Tailwind CSS + shadcn/ui
- Hardcoded values: 0 violations
- Bundle size: 398 KB (24% reduction)
- Lighthouse: 94 (23% improvement)
## Design System
All components now use CSS variables defined in globals.css:
- 24 color tokens
- 12 spacing tokens
- 8 typography tokens
- Full dark mode support
## Next Steps
- Deploy to production
- Monitor performance metrics
- Gather user feedback
- Optional: Implement additional shadcn blocksglobals.css// WRONG
<div style={{ backgroundColor: '#3b82f6' }}>
// RIGHT
<div className="bg-primary">// WRONG
<div style={{ padding: '20px', margin: '10px' }}>
// RIGHT
<div className="p-5 m-2.5">// WRONG
const CustomButton = styled.button`
background: #3b82f6;
padding: 8px 16px;
border-radius: 6px;
`
// RIGHT
import { Button } from '@/components/ui/button'// WRONG
<div style={{ color: 'red', fontSize: '14px' }}>
// RIGHT
<div className="text-destructive text-sm">// WRONG
<h1 style={{ fontFamily: 'Montserrat' }}>
// RIGHT
<h1 className="font-sans text-4xl font-bold">// WRONG
<span>❌ Delete</span>
// RIGHT
import { X } from 'lucide-react'
<Button variant="destructive">
<X className="mr-2 h-4 w-4" />
Delete
</Button>// WRONG
<div className="text-[#ff0000]">
// RIGHT
<div className="text-destructive">// Use semantic CSS variable names
bg-background
text-foreground
bg-primary
text-primary-foreground
bg-secondary
text-muted-foreground
border-border// Consistent spacing using Tailwind
p-2, p-4, p-6, p-8 // padding
m-2, m-4, m-6, m-8 // margin
gap-2, gap-4 // flex/grid gap// Build complex UIs by composing shadcn components
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'// Before building, ask MCP:
// "What shadcn component should I use for [use case]?"
// "Show me examples of shadcn [component]"// Create higher-level components that compose shadcn
export function FeatureCard({ feature }: { feature: Feature }) {
return (
<Card>
<CardHeader>
<CardTitle>{feature.title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">{feature.description}</p>
<Button className="mt-4">Learn More</Button>
</CardContent>
</Card>
)
}./scripts/analyze-codebase.py./scripts/detect-hardcoded-values.sh./scripts/init-nextjs-shadcn.sh./scripts/generate-migration-report.pybash ./scripts/init-nextjs-shadcn.sh my-appnpx shadcn@latest mcp init --client claudenpx shadcn@latest add [component]