</>
{ }
React · shadcn/ui · Radix UI · Tailwind CSS · Design System
shadcn/ui:
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.

15 Min. Lesezeit shadcn/ui · Radix UI · CSS-Variablen · Dark Mode · cva React 18 · React 19 · Tailwind CSS v4 · Next.js

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

11. FAQ: shadcn/ui Komponentenbibliothek

1Was ist shadcn/ui und wie unterscheidet es sich von MUI?
shadcn/ui ist kein npm-Package — es ist ein Copy-Paste-Katalog. Der Code gehört dem Projekt. MUI ist ein Package mit automatischen Updates, aber begrenzter Anpassbarkeit.
2Wie funktioniert das Theming?
CSS Custom Properties im :root-Selektor. Alle Farbtokens sind Variablen. Dark Mode durch Überschreiben derselben Variablen im .dark-Selektor.
3Was ist cva?
class-variance-authority — typsichere Komponentenvarianten. Deklarative Variantendefinition statt Ternary-Klassen-Verkettung, mit vollständigem TypeScript-Support.
4Was ist Radix UI?
Unstyled, vollständig accessible React-Primitives. Übernimmt ARIA, Keyboard-Navigation und Focus-Management. shadcn/ui ist styled Wrapper um Radix Primitives.
5Dark Mode Flash of Unstyled Content verhindern?
Inline Script im head liest localStorage synchron und setzt dark-Klasse vor Browser-Rendering. next-themes macht das automatisch für Next.js.
6shadcn/ui im Monorepo?
Ja. packages/ui-Package mit Turborepo oder nx. components.json konfiguriert Pfade. Alle Apps importieren aus dem zentralen UI-Package.
7Wie aktualisiere ich shadcn/ui-Komponenten?
Manuell mit dem diff-Befehl. Änderungen aus dem Katalog werden in eigene Komponenten gemergt. Bewusster Trade-off des Copy-Paste-Modells.
8Was macht cn()?
clsx für bedingte Klassen + tailwind-merge für Konfliktauflösung. Übergabe-Klassen gewinnen gegen Standard-Klassen — ohne !important.
9Storybook-Dokumentation für shadcn/ui?
CSS-Variablen in preview.css importieren, @storybook/addon-themes für Dark Mode. Stories für alle Varianten und Zustände. Autodocs aus TypeScript-Typen.
10shadcn/ui auch außerhalb von Next.js?
Ja — Vite, Remix, Astro und alle React-Setups werden unterstützt. CLI konfiguriert Framework-spezifische Pfade. Einige Next.js-spezifische Teile müssen angepasst werden.