shadcn/ui mit Claude Code: Copy-Paste Komponenten 2026

shadcn/ui ist kein klassisches NPM-Paket — es ist eine Sammlung von kopierbarem, vollständig anpassbarem Code auf Basis von Radix UI und Tailwind CSS. Claude Code kennt jede Komponente, jede CSS Variable und jeden Composition-Pattern.

Was ist shadcn/ui?

Wer shadcn/ui zum ersten Mal begegnet, ist verwirrt: Es gibt keine einzige NPM-Bibliothek namens shadcn-ui zu installieren. Stattdessen kopiert das CLI die Komponenten-Dateien direkt in dein Projekt — vollständig anpassbar, vollständig unter deiner Kontrolle.

KonzeptWas shadcn/ui wirklich ist

# shadcn/ui IST NICHT: # Ein NPM-Paket das du importierst # Eine fertige Komponentenbibliothek (wie MUI oder Chakra UI) # Etwas das du updaten musst (keine Breaking Changes durch Library-Updates) # shadcn/ui IST: # Eine Sammlung von Copy-Paste-faehigen Komponenten # Code in deinem Projekt (src/components/ui/) # Vollstaendig anpassbar (du besitzt den Code) # Basiert auf Radix UI Primitives (Accessibility eingebaut) # Styled mit Tailwind CSS und CSS Variables # Die Philosophie (aus der shadcn/ui Doku): # "Diese Komponenten sind dein Ausgangspunkt, nicht dein Endpunkt." # "Kopiere den Code. Mach ihn dir zu eigen. Passe ihn an deine Beduerfnisse an." # Was das CLI tatsaechlich macht: npx shadcn@latest add button # Legt an: src/components/ui/button.tsx # Der Code ist direkt in deinem Projekt # Keine Abhaengigkeit zu einem "shadcn" Package # Nur Abhaengigkeiten: @radix-ui/react-slot + class-variance-authority
Eigenschaftshadcn/uiMUI / Chakra UI
Code-OwnershipDu besitzt den CodeLibrary-Package
Anpassbarkeit100% (Code direkt editieren)Beschränkt auf Theme-API
Bundle-GrößeNur was du nutztGesamte Library
Breaking ChangesKeine (dein Code bleibt)Mit jedem Major Update
Styling-SystemTailwind + CSS VariablesEigenes Theme-System
AccessibilityRadix UI PrimitivesTeilweise
Dark ModeAutomatisch via CSS VariablesManuell konfigurieren
Claude Code Prompt: "Erkläre mir den Unterschied zwischen shadcn/ui und einer klassischen Komponentenbibliothek wie MUI. Was bedeutet Copy-Paste-First für unsere Codebasis?" — Claude erklärt die Philosophie und zeigt direkt passende Anwendungsfälle für dein Projekt.

Installation und Setup

shadcn/ui benötigt ein bestehendes Next.js- oder Vite-Projekt mit Tailwind CSS. Das Init-Kommando richtet alles ein — von der components.json bis zu den CSS Variables im globalen Stylesheet.

Setupshadcn/ui von Null in 5 Minuten

# 1. Next.js Projekt erstellen (oder bestehendes nutzen) npx create-next-app@latest my-app --typescript --tailwind --app cd my-app # 2. shadcn/ui initialisieren npx shadcn@latest init # CLI fragt: # Which style would you like to use? Default # Which color would you like to use as base color? Slate # Would you like to use CSS variables for colors? Yes # Ergebnis: components.json (Projekt-Konfiguration) { "$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 }, "aliases": { "components": "@/components", "utils": "@/lib/utils", "ui": "@/components/ui", "lib": "@/lib", "hooks": "@/hooks" } } # Was init ausserdem macht: # Fuegt CSS Variables in app/globals.css ein # Erstellt lib/utils.ts mit cn()-Hilfsfunktion # Konfiguriert tailwind.config.ts fuer shadcn-Farben # Installiert: tailwindcss-animate, class-variance-authority, clsx, tailwind-merge

UtilsDie cn()-Funktion — das Herzstück

# lib/utils.ts automatisch generiert import { clsx } from "clsx" import { twMerge } from "tailwind-merge" import type { ClassValue } from "clsx" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } # clsx fasst konditionelle Klassen zusammen # twMerge loest Tailwind-Konflikte auf (p-2 + p-4 wird nur p-4) # Beispiel: cn( "px-4 py-2 rounded-md", isActive && "bg-primary text-primary-foreground", isDisabled && "opacity-50 cursor-not-allowed", className // Props-Klassen ueberschreiben interne ) # Ergebnis: "px-4 py-2 rounded-md bg-primary text-primary-foreground"
Claude Code Prompt: "Richte shadcn/ui in meinem bestehenden Vite + React Projekt ein. Tailwind ist bereits konfiguriert." — Claude Code erkennt das bestehende Setup und passt den Init-Prozess entsprechend an, ohne Tailwind-Konfiguration zu überschreiben.

Komponenten hinzufügen

Jede Komponente wird einzeln zum Projekt hinzugefügt. Das CLI analysiert Abhängigkeiten und installiert automatisch alle benötigten Radix-Primitives.

KomponentenWas wird generiert — am Beispiel Button und Dialog

# Einzelne Komponenten hinzufuegen: npx shadcn@latest add button npx shadcn@latest add dialog npx shadcn@latest add table npx shadcn@latest add form # Mehrere auf einmal: npx shadcn@latest add button card badge input select # Was bei "add button" passiert: # Installiert: @radix-ui/react-slot # Erstellt: src/components/ui/button.tsx # Inhalt von src/components/ui/button.tsx: import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2", sm: "h-8 rounded-md px-3 text-xs", lg: "h-10 rounded-md px-8", icon: "h-9 w-9", }, }, defaultVariants: { variant: "default", size: "default", }, } ) # Nutzung im JSX: <Button variant="outline" size="sm">Abbrechen</Button> <Button variant="destructive">Loeschen</Button> <Button variant="ghost" size="icon"><TrashIcon /></Button>

DialogDialog-Komponente — Radix Composition Pattern

# Was "add dialog" installiert: # @radix-ui/react-dialog # src/components/ui/dialog.tsx # Nutzung im Code: import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog" export function DeleteDialog() { return ( <Dialog> <DialogTrigger asChild> <Button variant="destructive">Konto loeschen</Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Bist du sicher?</DialogTitle> <DialogDescription> Diese Aktion kann nicht rueckgaengig gemacht werden. </DialogDescription> </DialogHeader> <DialogFooter> <Button variant="outline">Abbrechen</Button> <Button variant="destructive">Endgueltig loeschen</Button> </DialogFooter> </DialogContent> </Dialog> ) } # asChild-Pattern: DialogTrigger rendert KEINEN eigenen Button # Nutzt stattdessen das Kind-Element als Trigger # Kein doppeltes Nesting von button in button
Claude Code Prompt: "Füge shadcn/ui Table, Form und DataTable zu unserem Projekt hinzu. Erstelle eine Beispiel-Tabelle mit Pagination für Benutzerdaten." — Claude Code fügt die Komponenten hinzu und generiert direkt eine vollständige DataTable-Implementierung mit TanStack Table.

Themes und CSS Variables

Das Theme-System von shadcn/ui basiert vollständig auf CSS Custom Properties. Dark Mode ist keine Nachbearbeitung — er ist von Anfang an eingebaut, weil alle Farben als semantische Variablen definiert sind.

ThemeCSS Variables — das komplette System

/* app/globals.css von shadcn/ui init generiert */ @layer base { :root { /* Hintergrundfarben */ --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; /* Karten */ --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; /* Popovers und Dropdowns */ --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; /* Primaerfarbe fuer Buttons, Links, aktive Elemente */ --primary: 222.2 47.4% 11.2%; --primary-foreground: 210 40% 98%; /* Sekundaerfarbe */ --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; /* Gedaempfte Elemente und Platzhalter */ --muted: 210 40% 96.1%; --muted-foreground: 215.4 16.3% 46.9%; /* Hover-Effekte */ --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; /* Fehler und gefahrliche Aktionen */ --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; /* Eingabefelder */ --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%; --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%; --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --destructive: 0 62.8% 30.6%; } }

Dark Modenext-themes Integration — automatisch

# Installation: 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 disableTransitionOnChange > {children} </ThemeProvider> ) } # app/layout.tsx: import { Providers } from "./providers" export default function RootLayout({ children }) { return ( <html lang="de" suppressHydrationWarning> <body> <Providers>{children}</Providers> </body> </html> ) } # Theme-Toggle Komponente: import { useTheme } from "next-themes" import { Sun, Moon } from "lucide-react" export function ThemeToggle() { const { theme, setTheme } = useTheme() return ( <Button variant="ghost" size="icon" onClick={() => setTheme(theme === "dark" ? "light" : "dark")}> <Sun className="dark:hidden" /> <Moon className="hidden dark:block" /> </Button> ) } # Warum das funktioniert: # next-themes setzt class="dark" auf das html-Element # .dark { --background: ... } ueberschreibt alle CSS Variables # ALLE shadcn-Komponenten nutzen diese Variables - Dark Mode automatisch
Wichtig: Die CSS Variables nutzen HSL-Werte ohne den hsl()-Wrapper — z.B. 222.2 84% 4.9% statt hsl(222.2, 84%, 4.9%). Das ist Absicht: Tailwind ergänzt das hsl() automatisch, sodass du Opacity-Modifier wie bg-primary/50 nutzen kannst.
Claude Code Prompt: "Erstelle ein benutzerdefiniertes shadcn/ui Theme in Brandfarben: Primär #6366f1, Sekundär #8b5cf6. Generiere alle nötigen CSS Variables für Light und Dark Mode." — Claude berechnet die HSL-Werte und erstellt die komplette globals.css-Anpassung.

Form-Komponenten mit React Hook Form + Zod

shadcn/ui liefert eine vollständige Form-Integration: Form, FormField, FormControl, FormLabel, FormMessage — alles zusammen mit React Hook Form und Zod-Validation.

FormKomplettes Login-Formular mit Validation

# Pakete installieren: npm install react-hook-form zod @hookform/resolvers npx shadcn@latest add form input button # components/login-form.tsx: "use client" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" // Zod-Schema definiert Felder und Validierungsregeln: const loginSchema = z.object({ email: z.string() .min(1, "E-Mail ist erforderlich") .email("Ungueltige E-Mail-Adresse"), password: z.string() .min(8, "Passwort muss mindestens 8 Zeichen haben") .max(100, "Passwort zu lang"), }) type LoginValues = z.infer<typeof loginSchema> export function LoginForm() { const form = useForm<LoginValues>({ resolver: zodResolver(loginSchema), defaultValues: { email: "", password: "" }, }) async function onSubmit(values: LoginValues) { // values ist vollstaendig typisiert und validiert await loginUser(values) } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>E-Mail</FormLabel> <FormControl> <Input placeholder="name@firma.de" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="password" render={({ field }) => ( <FormItem> <FormLabel>Passwort</FormLabel> <FormControl> <Input type="password" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit" className="w-full" disabled={form.formState.isSubmitting}> {form.formState.isSubmitting ? "Anmelden..." : "Anmelden"} </Button> </form> </Form> ) }

SelectSelect, Checkbox und komplexe Feldtypen

# Weitere Felder mit shadcn/ui Form-Komponenten: npx shadcn@latest add select checkbox switch textarea # Zod-Schema fuer komplexes Formular: const profileSchema = z.object({ name: z.string().min(2, "Name zu kurz"), role: z.enum(["admin", "editor", "viewer"], { required_error: "Bitte Rolle auswaehlen", }), notifications: z.boolean().default(false), bio: z.string().max(500, "Maximal 500 Zeichen").optional(), }) # FormField fuer Select: <FormField control={form.control} name="role" render={({ field }) => ( <FormItem> <FormLabel>Rolle</FormLabel> <Select onValueChange={field.onChange} defaultValue={field.value}> <FormControl> <SelectTrigger> <SelectValue placeholder="Rolle auswaehlen" /> </SelectTrigger> </FormControl> <SelectContent> <SelectItem value="admin">Administrator</SelectItem> <SelectItem value="editor">Editor</SelectItem> <SelectItem value="viewer">Betrachter</SelectItem> </SelectContent> </Select> <FormMessage /> </FormItem> )} /> # Cross-Field Validation mit z.refine(): const registerSchema = z.object({ password: z.string().min(8), confirmPassword: z.string(), }).refine((data) => data.password === data.confirmPassword, { message: "Passwoerter stimmen nicht ueberein", path: ["confirmPassword"], })
Claude Code Prompt: "Erstelle ein Registrierungsformular mit E-Mail, Passwort, Passwort-Bestätigung und AGBs-Checkbox. Nutze shadcn/ui Form-Komponenten mit Zod-Validation — Passwörter müssen übereinstimmen." — Claude Code kennt das z.refine()-Pattern für Cross-Field-Validation und implementiert es vollständig.

Eigene Komponenten erstellen

Das Mächtigste an shadcn/ui ist das Composition-Muster: Eigene Komponenten folgen denselben Konventionen wie die eingebauten — CVA für Variants, cn() für Klassen, Radix Primitives wo sinnvoll.

CVAClass Variance Authority — Variants wie die Profis

# CVA ist das Tool hinter shadcn/ui Button-Variants # Eigene Komponente mit denselben Patterns: # components/ui/status-badge.tsx: import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const statusBadgeVariants = cva( "inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-semibold", { variants: { status: { active: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300", inactive: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300", pending: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300", error: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300", }, size: { sm: "px-2 py-px text-[10px]", md: "px-2.5 py-0.5 text-xs", lg: "px-3 py-1 text-sm", }, }, defaultVariants: { status: "inactive", size: "md", }, } ) interface StatusBadgeProps extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof statusBadgeVariants> { showDot?: boolean } export function StatusBadge({ className, status, size, showDot = true, ...props }: StatusBadgeProps) { return ( <span className={cn(statusBadgeVariants({ status, size }), className)} {...props}> {showDot && <span className="h-1.5 w-1.5 rounded-full bg-current" />} {props.children} </span> ) } # Nutzung: <StatusBadge status="active">Aktiv</StatusBadge> <StatusBadge status="error" size="lg">Fehler</StatusBadge> <StatusBadge status="pending" showDot={false}>Ausstehend</StatusBadge>

CompositionCompound Components — Daten-Karte als Beispiel

# Eigene Compound-Komponente im shadcn/ui-Stil: # components/ui/stat-card.tsx import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { cn } from "@/lib/utils" interface StatCardProps { title: string value: string | number description?: string icon?: React.ReactNode trend?: { value: number; isPositive: boolean } className?: string } export function StatCard({ title, value, description, icon, trend, className }: StatCardProps) { return ( <Card className={cn("overflow-hidden", className)}> <CardHeader className="flex flex-row items-center justify-between pb-2"> <CardTitle className="text-sm font-medium text-muted-foreground"> {title} </CardTitle> {icon && <div className="text-muted-foreground">{icon}</div>} </CardHeader> <CardContent> <div className="text-2xl font-bold">{value}</div> {description && ( <p className="text-xs text-muted-foreground mt-1">{description}</p> )} {trend && ( <div className={cn( "flex items-center gap-1 text-xs mt-2 font-medium", trend.isPositive ? "text-green-600" : "text-red-600" )}> {trend.isPositive ? "aufwaerts" : "abwaerts"} {Math.abs(trend.value)}% <span className="text-muted-foreground font-normal">zum Vormonat</span> </div> )} </CardContent> </Card> ) } # Nutzung im Dashboard: <div className="grid gap-4 md:grid-cols-3"> <StatCard title="Monatlicher Umsatz" value="4.280 EUR" description="Gesamt im April 2026" trend={{ value: 12, isPositive: true }} /> <StatCard title="Aktive Nutzer" value="1.234" trend={{ value: 3, isPositive: false }} /> </div>

Registryshadcn/ui Registry — eigene Komponenten teilen

# Seit shadcn/ui 2.0: eigene Registry fuer Team-Komponenten # registry.json in deinem Projekt: { "$schema": "https://ui.shadcn.com/schema/registry.json", "name": "meine-firma", "homepage": "https://components.meine-firma.de", "items": [ { "name": "stat-card", "type": "registry:component", "description": "KPI-Karte mit Trend-Anzeige", "dependencies": ["lucide-react"], "registryDependencies": ["card"], "files": [ { "path": "registry/stat-card.tsx", "type": "registry:component" } ] } ] } # Komponente aus eigener Registry hinzufuegen: npx shadcn@latest add https://components.meine-firma.de/r/stat-card.json # Team-Mitglieder koennen sofort alle internen Komponenten nutzen # Gleicher Workflow wie offizielle shadcn/ui Komponenten # Versionierung ueber Git oder Package-Registry
Claude Code Prompt: "Erstelle eine vollständige DataTable-Komponente mit shadcn/ui Table, Sorting, Filtering und Pagination. Nutze TanStack Table als Basis und folge dem shadcn/ui Composition-Pattern." — Claude Code erstellt eine vollständige, typsichere DataTable die dem shadcn/ui-Stil exakt entspricht.

Fazit: shadcn/ui mit Claude Code

shadcn/ui ist der klügste Ansatz im UI-Ökosystem 2026: Kein Lock-in, volle Kontrolle, erstklassige Accessibility durch Radix, und ein Theme-System das Dark Mode kostenlos mitliefert. Claude Code kennt jeden Aspekt — von der CLI-Konfiguration über CVA-Patterns bis zu komplexen Form-Setups mit Zod.

Der entscheidende Vorteil gegenüber klassischen Komponentenbibliotheken: Wenn eine Komponente nicht exakt deinen Anforderungen entspricht, editierst du sie einfach. Kein API-Overhead, kein Warten auf Library-Updates, keine Konflikte mit deinem Design-System. Und Claude Code weiß genau, welche Stellen es zu ändern gilt.

UI-Modul im Kurs

Im Claude Code Mastery Kurs: vollständiges UI-Modul mit shadcn/ui Setup, Theme-Konfiguration, Form-Patterns mit React Hook Form + Zod, eigene Komponenten-Registry und DataTable-Implementierung — inkl. Dark Mode und Accessibility-Audit.

14 Tage kostenlos testen →