Requirements
- Target platform
- OpenClaw
- Install method
- Manual import
- Extraction
- Extract archive
- Prerequisites
- OpenClaw
- Primary doc
- SKILL.md
Use when building UI with shadcn/ui components, Tailwind CSS layouts, form patterns with react-hook-form and zod, theming, dark mode, sidebar layouts, mobile navigation, or any shadcn component question.
Use when building UI with shadcn/ui components, Tailwind CSS layouts, form patterns with react-hook-form and zod, theming, dark mode, sidebar layouts, mobile navigation, or any shadcn component question.
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete.
I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run.
Comprehensive guide for building production UIs with shadcn/ui, Tailwind CSS, react-hook-form, and zod.
shadcn/ui is not a component library β it's a collection of copy-paste components built on Radix UI primitives. You own the code. Components are added to your project, not installed as dependencies.
# Initialize shadcn/ui in a Next.js project npx shadcn@latest init # Add individual components npx shadcn@latest add button npx shadcn@latest add card npx shadcn@latest add dialog npx shadcn@latest add form npx shadcn@latest add input npx shadcn@latest add select npx shadcn@latest add table npx shadcn@latest add toast npx shadcn@latest add dropdown-menu npx shadcn@latest add sheet npx shadcn@latest add tabs npx shadcn@latest add sidebar # Add multiple at once npx shadcn@latest add button card input label textarea select checkbox
ComponentUse WhensidebarApp-level navigation with collapsible sectionsnavigation-menuTop-level site navigation with dropdownsbreadcrumbShowing page hierarchy/locationtabsSwitching between related views in same contextseparatorVisual divider between content sectionssheetSlide-out panel (mobile nav, filters, detail views)resizableAdjustable panel layouts
ComponentUse WhenformAny form with validation (wraps react-hook-form)inputText, email, password, number inputstextareaMulti-line text inputselectChoosing from a list (native-like)comboboxSearchable select (uses command + popover)checkboxBoolean or multi-select togglesradio-groupSingle selection from small setswitchOn/off toggle (settings, preferences)sliderNumeric range selectiondate-pickerDate selection (uses calendar + popover)togglePressed/unpressed state (toolbar buttons)
ComponentUse WhendialogModal confirmation, forms, or detail viewsalert-dialogDestructive action confirmation ("Are you sure?")sheetSide panel for forms, filters, mobile navtoastBrief non-blocking notifications (via sonner)alertInline status messages (info, warning, error)tooltipHover hints for icons/buttonspopoverRich content on click (color pickers, date pickers)hover-cardPreview content on hover (user profiles, links)skeletonLoading placeholdersprogressTask completion indicators
ComponentUse WhentableTabular data displaydata-tableTables with sorting, filtering, pagination (uses @tanstack/react-table)cardContent containers with header, body, footerbadgeStatus labels, tags, countsavatarUser profile imagesaccordionCollapsible FAQ or settings sectionscarouselImage/content slideshowsscroll-areaCustom scrollable containers
ComponentUse WhenbuttonPrimary actions, form submissionsdropdown-menuContext menus, action menuscontext-menuRight-click menusmenubarApplication menu barscommandCommand palette / search (βK)
npx shadcn@latest add form input select textarea checkbox button 'use client' import { zodResolver } from '@hookform/resolvers/zod' import { useForm } from 'react-hook-form' import { z } from 'zod' import { Button } from '@/components/ui/button' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Checkbox } from '@/components/ui/checkbox' import { toast } from 'sonner' const formSchema = z.object({ name: z.string().min(2, 'Name must be at least 2 characters'), email: z.string().email('Invalid email address'), role: z.enum(['admin', 'user', 'editor'], { required_error: 'Select a role' }), bio: z.string().max(500).optional(), notifications: z.boolean().default(false), }) type FormValues = z.infer<typeof formSchema> export function UserForm() { const form = useForm<FormValues>({ resolver: zodResolver(formSchema), defaultValues: { name: '', email: '', bio: '', notifications: false, }, }) async function onSubmit(values: FormValues) { try { await createUser(values) toast.success('User created successfully') form.reset() } catch (error) { toast.error('Failed to create user') } } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <FormField control={form.control} name="name" render={({ field }) => ( <FormItem> <FormLabel>Name</FormLabel> <FormControl> <Input placeholder="John Doe" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input type="email" placeholder="john@example.com" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="role" render={({ field }) => ( <FormItem> <FormLabel>Role</FormLabel> <Select onValueChange={field.onChange} defaultValue={field.value}> <FormControl> <SelectTrigger> <SelectValue placeholder="Select a role" /> </SelectTrigger> </FormControl> <SelectContent> <SelectItem value="admin">Admin</SelectItem> <SelectItem value="editor">Editor</SelectItem> <SelectItem value="user">User</SelectItem> </SelectContent> </Select> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="bio" render={({ field }) => ( <FormItem> <FormLabel>Bio</FormLabel> <FormControl> <Textarea placeholder="Tell us about yourself..." {...field} /> </FormControl> <FormDescription>Max 500 characters</FormDescription> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="notifications" render={({ field }) => ( <FormItem className="flex flex-row items-start space-x-3 space-y-0"> <FormControl> <Checkbox checked={field.value} onCheckedChange={field.onChange} /> </FormControl> <div className="space-y-1 leading-none"> <FormLabel>Email notifications</FormLabel> <FormDescription>Receive emails about account activity</FormDescription> </div> </FormItem> )} /> <Button type="submit" disabled={form.formState.isSubmitting}> {form.formState.isSubmitting ? 'Creating...' : 'Create User'} </Button> </form> </Form> ) }
'use client' import { useFormState } from 'react-dom' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' export function ContactForm() { const form = useForm<FormValues>({ resolver: zodResolver(schema), }) async function onSubmit(values: FormValues) { const formData = new FormData() Object.entries(values).forEach(([key, value]) => formData.append(key, String(value))) await submitContact(formData) } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> {/* fields */} </form> </Form> ) }
npm install next-themes npx shadcn@latest add dropdown-menu // app/providers.tsx 'use client' import { ThemeProvider } from 'next-themes' export function Providers({ children }: { children: React.ReactNode }) { return ( <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange> {children} </ThemeProvider> ) } // components/theme-toggle.tsx 'use client' import { Moon, Sun } from 'lucide-react' import { useTheme } from 'next-themes' import { Button } from '@/components/ui/button' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' export function ThemeToggle() { const { setTheme } = useTheme() return ( <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="outline" size="icon"> <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> </DropdownMenuTrigger> <DropdownMenuContent align="end"> <DropdownMenuItem onClick={() => setTheme('light')}>Light</DropdownMenuItem> <DropdownMenuItem onClick={() => setTheme('dark')}>Dark</DropdownMenuItem> <DropdownMenuItem onClick={() => setTheme('system')}>System</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> ) }
@layer base { :root { --background: 0 0% 100%; --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%; --ring: 222.2 84% 4.9%; --radius: 0.5rem; } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; --primary: 210 40% 98%; --primary-foreground: 222.2 47.4% 11.2%; /* ... etc */ } }
import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar' import { AppSidebar } from '@/components/app-sidebar' export default function DashboardLayout({ children }: { children: React.ReactNode }) { return ( <SidebarProvider> <AppSidebar /> <main className="flex-1"> <header className="flex h-14 items-center gap-4 border-b px-6"> <SidebarTrigger /> <h1 className="text-lg font-semibold">Dashboard</h1> </header> <div className="p-6">{children}</div> </main> </SidebarProvider> ) }
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet' import { Button } from '@/components/ui/button' import { Menu } from 'lucide-react' export function Header() { return ( <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur"> <div className="container flex h-14 items-center"> <div className="mr-4 hidden md:flex"> <Logo /> <nav className="flex items-center gap-6 text-sm ml-6"> <Link href="/dashboard">Dashboard</Link> <Link href="/settings">Settings</Link> </nav> </div> {/* Mobile hamburger */} <Sheet> <SheetTrigger asChild> <Button variant="outline" size="icon" className="md:hidden"> <Menu className="h-5 w-5" /> </Button> </SheetTrigger> <SheetContent side="left" className="w-[300px]"> <nav className="flex flex-col gap-4 mt-8"> <Link href="/dashboard">Dashboard</Link> <Link href="/settings">Settings</Link> </nav> </SheetContent> </Sheet> <div className="flex flex-1 items-center justify-end gap-2"> <ThemeToggle /> <UserMenu /> </div> </div> </header> ) }
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' export function StatsGrid({ stats }: { stats: Stat[] }) { return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"> {stats.map((stat) => ( <Card key={stat.label}> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle className="text-sm font-medium">{stat.label}</CardTitle> <stat.icon className="h-4 w-4 text-muted-foreground" /> </CardHeader> <CardContent> <div className="text-2xl font-bold">{stat.value}</div> <p className="text-xs text-muted-foreground">{stat.description}</p> </CardContent> </Card> ))} </div> ) }
// Centering <div className="flex items-center justify-center min-h-screen"> // Container with max-width <div className="container mx-auto px-4"> // Responsive grid <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> // Sticky header <header className="sticky top-0 z-50 border-b bg-background/95 backdrop-blur"> // Truncated text <p className="truncate">Very long text...</p> // Line clamp <p className="line-clamp-3">Multi-line truncation...</p> // Aspect ratio <div className="aspect-video rounded-lg overflow-hidden"> // Animations <div className="animate-pulse"> {/* Loading skeleton */} <div className="animate-spin"> {/* Spinner */} <div className="transition-all duration-200 hover:scale-105">
<Button>Default</Button> <Button variant="secondary">Secondary</Button> <Button variant="outline">Outline</Button> <Button variant="ghost">Ghost</Button> <Button variant="link">Link</Button> <Button variant="destructive">Delete</Button> <Button size="sm">Small</Button> <Button size="lg">Large</Button> <Button size="icon"><Plus className="h-4 w-4" /></Button> <Button disabled>Disabled</Button> <Button asChild><Link href="/page">As Link</Link></Button>
npx shadcn@latest add sonner // app/layout.tsx import { Toaster } from '@/components/ui/sonner' export default function RootLayout({ children }) { return ( <html><body>{children}<Toaster /></body></html> ) } // Usage anywhere import { toast } from 'sonner' toast.success('User created') toast.error('Something went wrong') toast.info('New update available') toast.warning('This action cannot be undone') toast.promise(asyncAction(), { loading: 'Creating...', success: 'Created!', error: 'Failed to create', })
'use client' import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from '@/components/ui/command' export function CommandPalette() { const [open, setOpen] = useState(false) const router = useRouter() useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { e.preventDefault() setOpen((open) => !open) } } document.addEventListener('keydown', down) return () => document.removeEventListener('keydown', down) }, []) return ( <CommandDialog open={open} onOpenChange={setOpen}> <CommandInput placeholder="Type a command or search..." /> <CommandList> <CommandEmpty>No results found.</CommandEmpty> <CommandGroup heading="Navigation"> <CommandItem onSelect={() => { router.push('/dashboard'); setOpen(false) }}> Dashboard </CommandItem> <CommandItem onSelect={() => { router.push('/settings'); setOpen(false) }}> Settings </CommandItem> </CommandGroup> </CommandList> </CommandDialog> ) }
Agent frameworks, memory systems, reasoning layers, and model-native orchestration.
Largest current source with strong distribution and engagement signals.