Eigene Komponentenbibliothek aufbauen
shadcn/ui ist keine Bibliothek im klassischen Sinne — es ist ein Katalog von Copy-Paste-Komponenten auf Radix-UI-Basis. Wer das Copy-Paste-Modell versteht, kann darauf eine vollständig kontrollierbare, themeable Komponentenbibliothek für das eigene Design System aufbauen.
Inhaltsverzeichnis
- 1. Das Copy-Paste-Modell verstehen
- 2. Projektsetup: shadcn/ui initialisieren
- 3. Theming mit CSS-Variablen
- 4. Dark Mode korrekt implementieren
- 5. Eigene Varianten mit cva und cn
- 6. Eigene Komponenten auf Radix-Basis
- 7. Dokumentation mit Storybook
- 8. Komponentenbibliothek im Monorepo
- 9. shadcn/ui vs. klassische Komponentenbibliotheken
- 10. Zusammenfassung
- 11. FAQ
1. Das Copy-Paste-Modell verstehen
shadcn/ui ist fundamental anders als MUI, Ant Design oder Chakra UI. Es ist kein npm-Package, das man installiert und importiert. Es ist ein Katalog von vorgefertigten Komponenten, die man mit einem CLI-Befehl in das eigene Projekt kopiert. Der Code gehört dann vollständig dem Projekt — keine Abhängigkeit, keine Breaking Changes bei Library-Updates, keine Constraints durch die Bibliotheks-API. Wer npx shadcn@latest add button ausführt, bekommt eine button.tsx-Datei in seinem Projekt, die er beliebig anpassen kann.
Dieses Modell hat einen entscheidenden Vorteil gegenüber klassischen Komponentenbibliotheken: Die Kontrolle liegt vollständig beim Entwickler. Es gibt keine Props, die die Bibliothek nicht vorgesehen hat, keine Styling-Konflikte zwischen Bibliotheks-CSS und eigenem CSS, und keine Versionierungsabhängigkeiten, die Upgrades blockieren. Der Preis: Updates aus dem shadcn/ui-Katalog kommen nicht automatisch. Wer Bugfixes oder neue Features möchte, muss sie manuell mergen. Das ist ein bewusster Trade-off — und für die meisten Teams der richtige.
Die Grundlage von shadcn/ui sind zwei Schichten: Radix UI als zugängliche, unstyled Primitive-Komponenten (Dialog, Dropdown, Select, Slider, Toast usw.) und Tailwind CSS für das Styling. Radix übernimmt die gesamte Accessibility-Arbeit (ARIA, Keyboard-Navigation, Focus-Management), Tailwind die visuelle Gestaltung. Die Komponenten aus shadcn/ui sind im Wesentlichen styled Wrapper um Radix-Primitives — eine Abstraktionsschicht, die man vollständig im eigenen Code hält.
2. Projektsetup: shadcn/ui initialisieren
Das Initialisieren von shadcn/ui in einem bestehenden oder neuen React-Projekt erfolgt mit npx shadcn@latest init. Der CLI fragt nach dem Framework (Next.js, Vite, Remix usw.), dem Stil (Default oder New York), der Base-Farbe und dem Pfad für die Komponenten. Das Ergebnis ist eine components.json im Projektwurzelverzeichnis, die alle Konfigurationsparameter enthält, sowie eine globals.css mit den CSS-Variablen für das Theming und ein lib/utils.ts mit der cn()-Hilfsfunktion.
Die cn()-Funktion kombiniert clsx für bedingte Klassen und tailwind-merge für die korrekte Auflösung von Tailwind-Klassen-Konflikten. Das ist das Herzstück der shadcn/ui-Styling-Architektur: Wenn eine Komponente Standard-Klassen hat und eine Anwendung eigene Klassen übergibt, löst tailwind-merge Konflikte zugunsten der spezifischeren Klasse auf. Das ermöglicht ein sauberes Override-Pattern ohne !important und ohne CSS-Spezifitätskämpfe.
// lib/utils.ts — the foundation of shadcn/ui styling
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
// Merges Tailwind classes intelligently — later classes win conflicts
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// components/ui/button.tsx — example of a shadcn/ui component
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';
// cva: define all visual variants declaratively
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-2 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',
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' },
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
}
);
Button.displayName = 'Button';
export { Button, buttonVariants };
3. Theming mit CSS-Variablen
Das Theming-System von shadcn/ui basiert vollständig auf CSS Custom Properties (CSS-Variablen). Alle Farben der Komponenten werden nicht als feste Tailwind-Farben definiert, sondern als Referenzen auf CSS-Variablen: bg-primary wird zu background-color: hsl(var(--primary)). Die CSS-Variablen selbst werden im :root-Selektor in der globals.css definiert und können für Dark Mode im .dark-Selektor überschrieben werden.
Für eine eigene Komponentenbibliothek bedeutet das: Durch das Ändern der CSS-Variablen lässt sich das gesamte visuelle Erscheinungsbild aller Komponenten ändern, ohne eine einzige Komponenten-Datei anzufassen. Ein Corporate-Design mit eigenen Primär- und Sekundärfarben wird vollständig über die CSS-Variablen implementiert. Für Multi-Tenant-Anwendungen, die verschiedene Themes für verschiedene Kunden anzeigen müssen, lassen sich Themes als CSS-Variablen-Sets implementieren, die dynamisch per JavaScript oder CSS-Klasse gewechselt werden.
/* globals.css — CSS variable theming system */
@layer base {
:root {
/* Base colors in HSL without the hsl() wrapper — enables opacity modifiers */
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%; /* custom brand blue */
--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: 221.2 83.2% 53.3%;
--radius: 0.5rem; /* global border-radius token */
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%; /* lighter shade for dark background */
--primary-foreground: 222.2 47.4% 11.2%;
/* ... all tokens redefined for dark mode */
}
}
4. Dark Mode korrekt implementieren
Das shadcn/ui-Theming-System macht Dark Mode verhältnismäßig einfach: Alle Farbtokens sind als CSS-Variablen definiert, die im .dark-Selektor überschrieben werden. Der einzige Moving Part: die .dark-Klasse muss zum richtigen Zeitpunkt an das html- oder body-Element hinzugefügt oder entfernt werden. In Next.js wird das mit der Bibliothek next-themes gehandhabt, die das Theme in localStorage speichert und System-Präferenzen berücksichtigt.
Ein häufiger Fehler beim Dark Mode ist das Flash of Unstyled Content (FOUC): Die Seite lädt kurz im Light Mode, bevor das gespeicherte Dark-Theme aus dem localStorage angewendet wird. Das passiert, weil JavaScript nach dem HTML-Parsing ausgeführt wird. Die Lösung: ein inline <script>-Tag im <head>, das synchron (blocking) das Theme aus localStorage liest und die Klasse setzt, bevor das Browser-Rendering beginnt. next-themes implementiert dieses Muster automatisch. Für eigene Implementierungen ohne next-themes ist das die kritische Stelle.
5. Eigene Varianten mit cva und cn
class-variance-authority (cva) ist der Typ-sichere Weg, Komponenten mit mehreren Varianten zu erstellen. Statt bedingte Klassen mit langen Ternary-Ausdrücken zu verketten, definiert man alle Varianten deklarativ in einem cva()-Aufruf. Das Ergebnis ist eine typsichere Funktion, die Props in Klassen umwandelt — mit vollständiger TypeScript-Unterstützung für alle definierten Varianten. Wer mit shadcn/ui arbeitet, lernt cva als fundamentales Werkzeug für die eigene Komponentenbibliothek.
Eigene Komponenten für das Design System werden nach demselben Muster gebaut wie die shadcn/ui-Basis-Komponenten: cva() für die Variantendefinition, cn() für das Class-Merging, VariantProps<typeof componentVariants> für typsichere Props. Wenn man eigene Komponenten nach diesem Pattern baut, sind sie sofort kompatibel mit dem shadcn/ui-Theming-System und unterstützen automatisch Dark Mode über die CSS-Variablen.
6. Eigene Komponenten auf Radix-Basis
Der wichtigste Vorteil von shadcn/ui als Basis: Man hat direkten Zugang zu Radix UI Primitives für eigene Komponenten. Radix stellt unstyled, vollständig accessible Primitives für alle gängigen UI-Patterns bereit — von einfachen Buttons und Checkboxen bis hin zu komplexen Dropdown-Menüs, Modals, Tooltips und Date-Pickern. Alle Radix-Primitives kümmern sich um ARIA-Attribute, Keyboard-Navigation und Focus-Management — Dinge, die korrekt zu implementieren Wochen dauern würden.
Eigene Komponenten auf Radix-Basis kombinieren Radix-Primitives mit cva-Variantendefinitionen und CSS-Variablen. Ein Custom-Accordion, eine Custom-NavigationMenu oder ein Combobox-Pattern folgen alle demselben Aufbau: Radix für die Logik und Accessibility, Tailwind für das Styling, cva für die Varianten, cn für das Merging. Das ergibt eine konsistente Komponentenbibliothek, bei der alle Komponenten dieselbe API-Philosophie teilen und im selben Theming-System leben.
// Custom component built on Radix primitives — same pattern as shadcn/ui
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const tooltipContentVariants = cva(
// Base styles — uses CSS variables for theming + dark mode support
'z-50 overflow-hidden rounded-md px-3 py-1.5 text-xs animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground',
muted: 'bg-muted text-muted-foreground border border-border',
destructive: 'bg-destructive text-destructive-foreground',
},
},
defaultVariants: { variant: 'default' },
}
);
interface TooltipContentProps
extends React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>,
VariantProps<typeof tooltipContentVariants> {}
// Wrap Radix primitive with custom styling — Radix handles all a11y
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
TooltipContentProps
>(({ className, variant, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(tooltipContentVariants({ variant, className }))}
{...props}
/>
</TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { TooltipContent };
7. Dokumentation mit Storybook
Storybook ist das Standard-Werkzeug für die Dokumentation von React-Komponentenbibliotheken und ergänzt shadcn/ui-basierte Design Systems ideal. Jede Komponente bekommt eine Story-Datei, die alle Varianten, Zustände und Use Cases visuell dokumentiert. In 2026 ist Storybook 8 mit dem neuen Controls-System und automatischer Dokumentationsgenerierung aus Component-Docstrings der Stand der Technik. Die Integration mit Tailwind CSS erfolgt über das offizielle PostCSS-Addon.
Für die Storybook-Integration mit shadcn/ui müssen die CSS-Variablen in der Storybook-Preview korrekt eingebunden werden. Das passiert über .storybook/preview.css, das die globals.css mit den Variabledefinitionen importiert. Dark-Mode-Stories können mit dem @storybook/addon-themes-Addon implementiert werden: Ein Theme-Switcher in der Storybook-Toolbar wechselt zwischen Light und Dark Mode, indem er die .dark-Klasse am Story-Container toggled.
8. Komponentenbibliothek im Monorepo
Wenn die shadcn/ui-basierte Komponentenbibliothek von mehreren Projekten genutzt werden soll — etwa in einem Monorepo mit mehreren Next.js-Apps und einer Expo-App — empfiehlt sich ein dediziertes Package im Monorepo. Mit Turborepo oder nx als Monorepo-Tool und einem packages/ui-Package lässt sich die Komponentenbibliothek einmal definieren und von allen Apps importieren. Shadcn/ui hat seit Version 2 eine explizite Monorepo-Dokumentation und unterstützt den components.json-Pfad-Konfigurationsparameter für Monorepo-Setups.
Die wichtigste Entscheidung bei einem Monorepo-Setup: ob Tailwind-Klassen im UI-Package gebündelt werden sollen oder ob jede App ihre eigene Tailwind-Konfiguration für das UI-Package bereitstellt. Das letztere Modell ist flexibler — jede App kann das Design System themen — aber erfordert, dass alle Apps die Tailwind-Konfiguration des UI-Packages in ihrem content-Array referenzieren. Das erstere Modell ist einfacher, aber weniger flexibel für Multi-Branding-Anforderungen.
9. shadcn/ui vs. klassische Komponentenbibliotheken
Die Entscheidung zwischen shadcn/ui und klassischen Komponentenbibliotheken hängt von den Projektanforderungen und dem Team ab. Beide Ansätze haben ihre Berechtigung — die folgende Tabelle hilft bei der Entscheidungsfindung.
| Kriterium | shadcn/ui | MUI / Ant Design | Chakra UI |
|---|---|---|---|
| Kontrolle über Code | Vollständig | Keine (npm-Package) | Keine (npm-Package) |
| Theming-Aufwand | CSS-Variablen, minimal | Theme-Objekt, komplex | Theme-Objekt, mittel |
| Bundle-Größe | Nur genutzte Komponenten | Tree-shaking, aber größer | Mittel |
| Updates automatisch | Nein — manuelles Merge | Ja (npm update) | Ja (npm update) |
| Komponentenanzahl | ~50 Basis-Komponenten | 200+ Komponenten | 80+ Komponenten |
10. Zusammenfassung
shadcn/ui als Basis für eine eigene Komponentenbibliothek ist in 2026 einer der beliebtesten Ansätze für React-Design-Systeme — und das aus gutem Grund. Das Copy-Paste-Modell gibt Teams vollständige Kontrolle über den Komponentencode, ohne auf die Accessibility-Grundlagen von Radix UI und das mächtige Theming-System mit CSS-Variablen verzichten zu müssen. Eigene Komponenten lassen sich nach demselben Pattern mit cva, cn und Radix-Primitives aufbauen und integrieren sich nahtlos in das bestehende Theming.
Der entscheidende Vorteil von shadcn/ui gegenüber klassischen Bibliotheken: keine Versionierungsabhängigkeiten, keine Styling-Konflikte, kein Theming-Overhead. Der entscheidende Nachteil: Updates müssen manuell gemergt werden, und für komplexe Komponenten, die shadcn/ui nicht abdeckt, muss man direkt auf Radix-Primitives zurückgreifen. Für Teams, die ein professionelles Design System mit vollständiger Kontrolle aufbauen wollen, ist der Ansatz einer der besten aktuellen Optionen.
shadcn/ui Komponentenbibliothek — Das Wichtigste auf einen Blick
Copy-Paste-Modell
Code gehört dem Projekt — keine Library-Abhängigkeit, keine Breaking Changes, vollständige Kontrolle über jede Komponente.
CSS-Variablen-Theming
Alle Farbtokens als CSS Custom Properties — Dark Mode und Multi-Branding durch Variablen-Override, keine Komponenten-Änderungen nötig.
cva + cn Pattern
class-variance-authority für typsichere Varianten, tailwind-merge für konfliktfreies Klassen-Overriding. Eigene Komponenten auf demselben Pattern aufbauen.
Radix UI Primitives
Unstyled, vollständig accessible Primitive für alle gängigen UI-Patterns. ARIA, Keyboard-Navigation und Focus-Management inklusive.
Mironsoft
React Design Systems, shadcn/ui-Komponentenbibliotheken und Storybook-Dokumentation
Eigene React-Komponentenbibliothek aufbauen?
Wir bauen auf shadcn/ui-Basis vollständig kontrollierbare, themeable Komponentenbibliotheken für React-Projekte — mit Storybook-Dokumentation, Monorepo-Setup und Corporate-Design-Integration.
Design System
shadcn/ui-Basis mit eigenem Corporate-Theme, CSS-Variablen und Dark-Mode-Support
Storybook-Setup
Vollständige Komponentendokumentation mit Stories, Controls und Dark-Mode-Vorschau
Monorepo-Integration
packages/ui-Setup mit Turborepo und Multi-App-Theming für komplexe Monorepo-Strukturen