toolchains/ui/components/shadcn/SKILL.md
shadcn/ui component library for React with Tailwind CSS - copy-paste accessible components with full code ownership
npx skillsauth add bobmatnyc/claude-mpm-skills shadcn-uiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
shadcn/ui is a collection of re-usable React components built with Radix UI primitives and styled with Tailwind CSS. Unlike traditional component libraries, shadcn/ui components are copied directly into your project, giving you full ownership and customization control. Components are accessible, customizable, and open source.
Core Philosophy: Copy-paste components, not npm packages. You own the code.
Use shadcn/ui when:
Don't use when:
# Initialize shadcn/ui in your project
npx shadcn-ui@latest init
# Follow interactive prompts:
# - TypeScript? (yes/no)
# - Style: Default/New York
# - Base color: Slate/Gray/Zinc/Neutral/Stone
# - CSS variables: (yes/no)
# - React Server Components: (yes/no)
# - components.json location
# - Tailwind config location
# - CSS file location
# - Import alias (@/components)
# Add individual components
npx shadcn-ui@latest add button
npx shadcn-ui@latest add card
npx shadcn-ui@latest add dialog
# Add multiple components at once
npx shadcn-ui@latest add button card dialog form input
import { Button } from "@/components/ui/button"
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
export default function Example() {
return (
<Card>
<CardHeader>
<CardTitle>Welcome</CardTitle>
</CardHeader>
<CardContent>
<Button>Click me</Button>
</CardContent>
</Card>
)
}
Key Difference from Traditional Libraries:
npm install component-library → locked to package versionscomponents/ui/ → you own the codeBenefits:
src/
├── components/
│ └── ui/
│ ├── button.tsx # Component implementation
│ ├── card.tsx # Owns its code
│ ├── dialog.tsx # Modifiable
│ └── ...
├── lib/
│ └── utils.ts # cn() helper for class merging
└── app/
└── globals.css # Tailwind directives + CSS variables
Core Dependencies:
Radix UI Integration:
// shadcn/ui components wrap Radix primitives
import * as DialogPrimitive from "@radix-ui/react-dialog"
// Add styling and variants
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogContent = React.forwardRef<...>(
({ className, children, ...props }, ref) => (
<DialogPrimitive.Content
ref={ref}
className={cn("fixed ...", className)}
{...props}
/>
)
)
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
Key Options:
style: "default" or "new-york" (design variants)rsc: React Server Components supportcssVariables: Use CSS variables for themingprefix: Tailwind class prefix (optional)// tailwind.config.ts
import type { Config } from "tailwindcss"
const config = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
} satisfies Config
export default config
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
import { Button } from "@/components/ui/button"
// Variants
<Button variant="default">Default</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
// Sizes
<Button size="default">Default</Button>
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>
<Button size="icon"><Icon /></Button>
// States
<Button disabled>Disabled</Button>
<Button asChild>
<Link href="/about">As Link</Link>
</Button>
Implementation Pattern (CVA):
import { cva, type VariantProps } from "class-variance-authority"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
import {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
} from "@/components/ui/card"
<Card>
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card description goes here</CardDescription>
</CardHeader>
<CardContent>
<p>Card content</p>
</CardContent>
<CardFooter>
<Button>Action</Button>
</CardFooter>
</Card>
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogFooter,
} from "@/components/ui/dialog"
<Dialog>
<DialogTrigger asChild>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>
This action cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline">Cancel</Button>
<Button>Confirm</Button>
</DialogFooter>
</DialogContent>
</Dialog>
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
email: z.string().email({
message: "Please enter a valid email address.",
}),
})
function ProfileForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
email: "",
},
})
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" placeholder="[email protected]" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>INV001</TableCell>
<TableCell>Paid</TableCell>
<TableCell>Credit Card</TableCell>
<TableCell className="text-right">$250.00</TableCell>
</TableRow>
</TableBody>
</Table>
Available via CLI:
accordion - Collapsible content sectionsalert - Contextual feedback messagesalert-dialog - Interrupting modal dialogsavatar - User profile imagesbadge - Status indicatorscalendar - Date pickercheckbox - Binary inputcommand - Command palette (⌘K menu)context-menu - Right-click menusdropdown-menu - Dropdown menushover-card - Hover tooltipsinput - Text inputlabel - Form labelsmenubar - Application menu barnavigation-menu - Site navigationpopover - Floating panelsprogress - Progress indicatorsradio-group - Radio button groupsscroll-area - Custom scrollbarsselect - Dropdown selectsseparator - Visual dividerssheet - Side panelsskeleton - Loading placeholdersslider - Range inputswitch - Toggle switchtabs - Tab navigationtextarea - Multi-line inputtoast - Notification toaststoggle - Toggle buttontooltip - Hover tooltipsChange base color scheme:
# Regenerate components with new base color
npx shadcn-ui@latest init
# Choose new base: Slate, Gray, Zinc, Neutral, Stone
Manual color override (globals.css):
:root {
--primary: 210 100% 50%; /* HSL: Blue */
--primary-foreground: 0 0% 100%;
}
.dark {
--primary: 210 100% 60%; /* Lighter blue for dark mode */
}
// Extend button variants
const buttonVariants = cva(
"...",
{
variants: {
variant: {
// ...existing variants
gradient: "bg-gradient-to-r from-purple-500 to-pink-500 text-white",
},
},
}
)
// Usage
<Button variant="gradient">Gradient Button</Button>
// Using next-themes
import { ThemeProvider } from "next-themes"
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
)
}
// Theme toggle component
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
export function ThemeToggle() {
const { setTheme, theme } = useTheme()
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
)
}
npm install next-themes
// app/providers.tsx
"use client"
import { ThemeProvider } from "next-themes"
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
)
}
// app/layout.tsx
import { Providers } from "./providers"
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
// Force dark mode for specific section
<div className="dark">
<Card>Always dark, regardless of theme</Card>
</div>
// Conditional styling
<div className="bg-white dark:bg-slate-950">
<p className="text-slate-900 dark:text-slate-50">
Adapts to theme
</p>
</div>
# Create Next.js app with TypeScript and Tailwind
npx create-next-app@latest my-app --typescript --tailwind --app
# Initialize shadcn/ui
cd my-app
npx shadcn-ui@latest init
# Add components
npx shadcn-ui@latest add button card form
// app/page.tsx (Server Component by default)
import { Button } from "@/components/ui/button"
export default function HomePage() {
return (
<main>
<h1>Welcome</h1>
{/* Static components work in Server Components */}
<Button asChild>
<a href="/about">Learn More</a>
</Button>
</main>
)
}
// app/interactive.tsx
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"
export function InteractiveSection() {
const [open, setOpen] = useState(false)
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogContent>
<p>Client-side interactivity</p>
</DialogContent>
</Dialog>
)
}
// app/api/submit/route.ts
import { NextResponse } from "next/server"
import { z } from "zod"
const formSchema = z.object({
email: z.string().email(),
message: z.string().min(10),
})
export async function POST(request: Request) {
try {
const body = await request.json()
const validatedData = formSchema.parse(body)
// Process form data
return NextResponse.json({ success: true })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ errors: error.errors }, { status: 400 })
}
return NextResponse.json({ error: "Internal error" }, { status: 500 })
}
}
All shadcn/ui components include proper ARIA attributes via Radix UI:
// Dialog automatically includes:
// - role="dialog"
// - aria-describedby
// - aria-labelledby
// - Focus trap
// - Escape key handler
<Dialog>
<DialogContent>
{/* Automatically accessible */}
</DialogContent>
</Dialog>
// Button includes:
// - role="button"
// - tabindex="0"
// - Keyboard activation (Space/Enter)
<Button>Accessible by default</Button>
Built-in keyboard support:
Tab / Shift+Tab - Navigate between interactive elementsEnter / Space - Activate buttonsEscape - Close dialogs, dropdowns, popoversArrow keys - Navigate menus, select options, radio groupsHome / End - Jump to first/last in listsExample: Command Palette:
import {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
} from "@/components/ui/command"
// ⌘K to open
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>Calendar</CommandItem>
<CommandItem>Search Emoji</CommandItem>
<CommandItem>Calculator</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
// Visually hidden but accessible to screen readers
<span className="sr-only">Close dialog</span>
// Skip navigation links
<a href="#main-content" className="sr-only focus:not-sr-only">
Skip to main content
</a>
// Descriptive labels
<FormLabel htmlFor="email">Email address</FormLabel>
<Input
id="email"
type="email"
aria-describedby="email-description"
aria-invalid={!!errors.email}
/>
<FormDescription id="email-description">
We'll never share your email.
</FormDescription>
// Focus trap in Dialog (automatic)
<Dialog>
<DialogContent>
{/* Focus stays within dialog until closed */}
</DialogContent>
</Dialog>
// Custom focus management
import { useRef, useEffect } from "react"
function CustomComponent() {
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
inputRef.current?.focus()
}, [])
return <Input ref={inputRef} />
}
// Card composition
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
<CardDescription>Description</CardDescription>
</CardHeader>
<CardContent>Content</CardContent>
<CardFooter>Footer</CardFooter>
</Card>
// Form composition
<Form {...form}>
<FormField
control={form.control}
name="field"
render={({ field }) => (
<FormItem>
<FormLabel>Label</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>Help text</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</Form>
// Render Button as Link
import { Button } from "@/components/ui/button"
import Link from "next/link"
<Button asChild>
<Link href="/dashboard">Go to Dashboard</Link>
</Button>
// Render as custom component
<Button asChild>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
Animated Button
</motion.button>
</Button>
How it works (Radix Slot):
import { Slot } from "@radix-ui/react-slot"
interface ButtonProps {
asChild?: boolean
}
const Button = ({ asChild, ...props }: ButtonProps) => {
const Comp = asChild ? Slot : "button"
return <Comp {...props} />
}
// Create custom card variant
export function PricingCard({
title,
price,
features,
highlighted
}: PricingCardProps) {
return (
<Card className={cn(highlighted && "border-primary")}>
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription className="text-3xl font-bold">
${price}/mo
</CardDescription>
</CardHeader>
<CardContent>
<ul className="space-y-2">
{features.map((feature) => (
<li key={feature} className="flex items-center">
<Check className="mr-2 h-4 w-4 text-primary" />
{feature}
</li>
))}
</ul>
</CardContent>
<CardFooter>
<Button className="w-full" variant={highlighted ? "default" : "outline"}>
Get Started
</Button>
</CardFooter>
</Card>
)
}
# Interactive init
npx shadcn-ui@latest init
# Non-interactive with defaults
npx shadcn-ui@latest init -y
# Specify options
npx shadcn-ui@latest init --typescript --tailwind
# Single component
npx shadcn-ui@latest add button
# Multiple components
npx shadcn-ui@latest add button card dialog form
# All components (not recommended - adds everything)
npx shadcn-ui@latest add --all
# Specific version
npx shadcn-ui@latest add [email protected]
# Overwrite existing
npx shadcn-ui@latest add button --overwrite
# Different path
npx shadcn-ui@latest add button --path src/components/ui
# Check for component updates
npx shadcn-ui@latest diff
# Diff specific component
npx shadcn-ui@latest diff button
# Show what would change
npx shadcn-ui@latest diff --check
# Update all components
npx shadcn-ui@latest update
# Update specific components
npx shadcn-ui@latest update button card
# Preview changes before applying
npx shadcn-ui@latest update --dry-run
// useToast hook (built-in with toast component)
import { useToast } from "@/components/ui/use-toast"
function MyComponent() {
const { toast } = useToast()
return (
<Button
onClick={() => {
toast({
title: "Scheduled: Catch up",
description: "Friday, February 10, 2023 at 5:57 PM",
})
}}
>
Show Toast
</Button>
)
}
// Custom form hook
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
function useFormWithToast<T extends z.ZodType>(schema: T) {
const { toast } = useToast()
const form = useForm({
resolver: zodResolver(schema),
})
const handleSubmit = form.handleSubmit(async (data) => {
try {
// Submit logic
toast({ title: "Success!" })
} catch (error) {
toast({ title: "Error", variant: "destructive" })
}
})
return { form, handleSubmit }
}
// Mobile-first responsive components
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<Card>Mobile: 1 col, Tablet: 2 col, Desktop: 3 col</Card>
</div>
// Responsive dialog (sheet on mobile, dialog on desktop)
import { useMediaQuery } from "@/hooks/use-media-query"
import { Dialog, DialogContent } from "@/components/ui/dialog"
import { Sheet, SheetContent } from "@/components/ui/sheet"
function ResponsiveModal({ children, ...props }) {
const isDesktop = useMediaQuery("(min-width: 768px)")
if (isDesktop) {
return (
<Dialog {...props}>
<DialogContent>{children}</DialogContent>
</Dialog>
)
}
return (
<Sheet {...props}>
<SheetContent>{children}</SheetContent>
</Sheet>
)
}
// Using Framer Motion with shadcn/ui
import { motion } from "framer-motion"
import { Card } from "@/components/ui/card"
const MotionCard = motion(Card)
<MotionCard
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
Animated Card
</MotionCard>
// Staggered list animation
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.1
}
}
}
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 }
}
<motion.ul variants={container} initial="hidden" animate="show">
{items.map((item) => (
<motion.li key={item.id} variants={item}>
<Card>{item.content}</Card>
</motion.li>
))}
</motion.ul>
Code Organization:
components/ui/ (don't mix with app components)components/ (outside ui/)lib/utils.ts for shared utilitiesCustomization:
components.json manually (use CLI)Performance:
asChild to avoid unnecessary wrapper elementsAccessibility:
asChild when applicableTypeScript:
Import errors:
# Check path aliases in tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
Tailwind classes not applying:
// Ensure content paths include your components
// tailwind.config.ts
content: [
'./src/components/**/*.{ts,tsx}', // Add this
'./src/app/**/*.{ts,tsx}',
]
Dark mode not working:
// Add suppressHydrationWarning to <html>
<html lang="en" suppressHydrationWarning>
Form validation not triggering:
// Ensure FormMessage is included in FormField
<FormField>
<FormItem>
<FormControl>...</FormControl>
<FormMessage /> {/* Required for errors */}
</FormItem>
</FormField>
development
Axum (Rust) web framework patterns for production APIs: routers/extractors, state, middleware, error handling, tracing, graceful shutdown, and testing
development
Optimize web performance using Core Web Vitals, modern patterns (View Transitions, Speculation Rules), and framework-specific techniques
development
Best practices for documenting APIs and code interfaces, eliminating redundant documentation guidance per agent.
development
Comprehensive API design patterns covering REST, GraphQL, gRPC, versioning, authentication, and modern API best practices