Zum Inhalt springen
    WingoUI
    KomponentenVorlagenDokuPreiseKI-AgentenBlogChangelog
    1. Startseite
    2. Blog
    3. Next.js und Tailwind CSS
    4. Tailwind v4 Design Tokens: Farben, Dark Mode, Kontrast
    Read in English
    1. Blog
    2. Next.js und Tailwind CSS
    3. Tailwind v4 Design Tokens: Farben, Dark Mode, Kontrast
    Next.js und Tailwind CSS
    Next.js und Tailwind CSS

    Tailwind v4 Design Tokens: Farben, Dark Mode, Kontrast

    SR
    Serban Rusu · Gründer von Wingo UI
    9. Okt. 2026 · 20 Min. Lesezeit

    Auf dieser Seite

    0%
    1. Was sind Design Tokens in Tailwind v4?
    2. Wie solltest du Tailwind v4 Design Tokens in Schichten aufbauen?
    3. Wann brauchst du Tailwind v4 @theme inline?
    4. Welche semantischen Farb-Tokens braucht eine Komponentenbibliothek?
      1. Flächen, von hinten nach vorn
      2. Textstufen
      3. Tints statt grauer Füllungen
    5. Wie funktionieren Statusfarben ohne eine Klasse pro Farbe?
      1. Komponenten-Variablen
    6. Wie gestaltest du Dark Mode mit Tokens?
    7. Wie prüfst du den Kontrast für jedes Token-Paar?
    8. Solltest du Tailwind v4 OKLCH-Werte oder Hex schreiben?
    9. Wie werden Radius, Elevation, z-index und Motion zu Tokens?
      1. Der Radius folgt der Größe
      2. Drei Elevation-Stufen
      3. Stapelebenen
      4. Motion
    10. Können Komponenten-Props wie Design Tokens funktionieren?
    11. Wie bringst du diese Tokens in ein bestehendes Next.js-Projekt?
    12. Welche Fehler machen ein Tailwind v4 Design System kaputt?
    13. Wo solltest du anfangen?
    14. Häufige Fragen
      1. Wohin gehören Design Tokens in Tailwind v4?
      2. Was ist der Unterschied zwischen @theme und @theme inline in Tailwind v4?
      3. Sollten Farben in Tailwind v4 OKLCH oder Hex sein?
      4. Wie bekommen Tailwind v4 Design Tokens einen Dark Mode?
      5. Wie prüfe ich den Farbkontrast von Design Tokens?
      6. Sind die Design Tokens von Wingo UI kostenlos?

    Kurz gesagt

    Tailwind v4 Design Tokens sind CSS-Variablen: Werte, die sich zwischen Light und Dark ändern, stehen in :root und .dark und werden mit @theme inline auf Utilities gemappt, sodass bg-surface oder text-muted-foreground ohne dark:-Präfix dem Theme folgen. Ein vollständiges Set benennt Rollen statt Farben: Flächen in der Reihenfolge, in der sie sich stapeln, Textstufen, Tones mit getrennter Füllung und lesbarer Textfarbe, dazu Skalen für Radius, Elevation, z-index und Motion. Prüf jedes Paar aus Text und Fläche in beiden Themes per Skript auf 4.5:1, denn eine Farbe, die auf Weiß besteht, scheitert oft auf einem dunklen Popover.

    Veröffentlicht am 9. Okt. 2026

    Tailwind CSS v4 hat die Konfiguration aus der tailwind.config.js ins CSS verlegt, und viele Guides hören beim ersten Schritt auf: ein paar Markenfarben in @theme packen und das Ganze Tokens nennen. Eine Komponentenbibliothek braucht mehr als eine Palette. Sie braucht Tailwind v4 Design Tokens für jede Entscheidung, die eine Komponente trifft: welche Fläche vor welcher liegt, wie blass sekundärer Text werden darf, was eine Warnung als Füllung und was sie als Text nutzt, wie rund ein 32px-Button neben einem 48px-Button ist, wie ein Popover auf einer schwarzen Seite einen Schatten wirft und auf welcher Ebene sich ein Select innerhalb eines Dialogs öffnet. Dieser Guide zeigt das Token-System, das wir in Wingo UI nutzen, einer Komponentenbibliothek für React 19 und Tailwind CSS v4, mit dem echten CSS und den Kontrastwerten hinter jeder Entscheidung. Wir bauen Wingo UI, sind also nicht neutral; Fakten von außen sind mit ihrer Quelle verlinkt und datiert.

    Was sind Design Tokens in Tailwind v4?

    Design Tokens sind in Tailwind v4 CSS Custom Properties. Die Tailwind v4 Theme-Variablen, die du in einem @theme-Block deklarierst, haben zwei Aufgaben zugleich: Sie sind CSS-Variablen, die du mit var() lesen kannst, und ihr Namespace entscheidet, welche Utilities Tailwind erzeugt. --color-surface erzeugt bg-surface, text-surface und border-surface; --shadow-raised erzeugt shadow-raised. Tailwind CSS v4.0 hat diese CSS-first-Konfiguration am 22. Januar 2025 ausgeliefert (Release-Post (opens in a new tab)), und die Doku zu Theme-Variablen (opens in a new tab) listet die Namespaces. Wir nutzen Stand Oktober 2026 Tailwind 4.3.3.

    Die Namespaces, die eine Komponentenbibliothek am häufigsten nutzt:

    NamespaceUtilities, die er erzeugtWas wir dort ablegen
    --color-*bg-*, text-*, border-*, ring-*, fill-*Flächen, Textstufen, Tones, Rahmen, den Fokusring
    --shadow-*shadow-*drei Elevation-Stufen
    --radius-*rounded-*eine Legacy-Skala, die neuer Code meidet (siehe Abschnitt zum Radius)
    --z-index-*z-*sieben Stapelebenen
    --ease-*, --transition-duration-*ease-*, duration-*Motion-Kurven und Dauern
    --font-*font-*Schriftfamilien
    --spacingp-*, m-*, gap-*, w-*, h-*das 4px-Raster (0.25rem)

    --z-index-* und --transition-duration-* stehen Stand Oktober 2026 nicht in der Tabelle der Doku, Tailwind 4.3 kompiliert sie aber: Unser CSS enthält .z-modal { z-index: var(--z-modal) }.

    Wie solltest du Tailwind v4 Design Tokens in Schichten aufbauen?

    Nutz drei Schichten. Rohwerte, die sich zwischen den Themes ändern, stehen in :root und .dark. Ein Mapping in @theme inline macht daraus Utilities. Komponenten beziehen ihre Farben dann aus ein paar Laufzeit-Variablen, die ein Prop setzt, sodass ein tone oder eine eigene color nie eine eigene Klasse braucht.

    Hier ein gekürzter, aber lauffähiger Auszug aus unserer app/globals.css, mit den echten Werten:

    css
    @import "tailwindcss";
    @custom-variant dark (&:is(.dark *));
    :root {
    --background: oklch(1 0 0);
    --foreground: oklch(0.3211 0 0);
    --surface: #ffffff;
    --popover: oklch(1 0 0);
    --muted-foreground: oklch(0.52 0.0234 264.3637);
    --success: #15803d;
    --success-foreground: #ffffff;
    --success-soft: #e7f6ec;
    --success-soft-foreground: #166534;
    --elevation-raised: 0 0 0 1px rgb(0 0 0 / 0.07), 0 1px 2px rgb(0 0 0 / 0.05), 0 2px 8px -2px rgb(0 0 0 / 0.06);
    }
    .dark {
    --background: oklch(0 0 0);
    --foreground: oklch(0.9219 0 0);
    --surface: #1a1a1a;
    --popover: #262626;
    --muted-foreground: oklch(0.7155 0 0);
    --success-soft: #0d2315;
    --success-soft-foreground: #4ade80;
    --elevation-raised: inset 0 1px 0 0 rgb(255 255 255 / 0.06), 0 0 0 1px rgb(255 255 255 / 0.08), 0 1px 2px rgb(0 0 0 / 0.5);
    }
    @theme inline {
    --color-background: var(--background);
    --color-foreground: var(--foreground);
    --color-surface: var(--surface);
    --color-popover: var(--popover);
    --color-muted-foreground: var(--muted-foreground);
    --color-success: var(--success);
    --color-success-foreground: var(--success-foreground);
    --color-success-soft: var(--success-soft);
    --color-success-soft-foreground: var(--success-soft-foreground);
    --shadow-raised: var(--elevation-raised);
    }

    Die Success-Füllung und ihr weißer Text sind in beiden Themes gleich (5.02:1), deshalb deklariert dieser Auszug sie nur in :root, und .dark erbt sie. Alles, was sich ändert, wird unter .dark neu deklariert, und eine Komponente nutzt überall dieselben Klassen:

    tsx
    export function InvoiceRow() {
    return (
    <div className="flex items-center justify-between rounded-2xl bg-surface p-4 text-foreground shadow-raised">
    <div>
    <p className="text-[15px] font-medium">Rechnung 1042</p>
    <p className="text-sm text-muted-foreground">Emma Carter, New York</p>
    </div>
    <span className="rounded-full bg-success-soft px-2 py-0.5 text-xs font-medium text-success-soft-foreground">Bezahlt</span>
    </div>
    );
    }

    Die Rohnamen folgen bewusst der Konvention von shadcn/ui: --background, --card, --popover und --muted-foreground sind shadcn-Namen, und die, die shadcn nicht hat (--surface, --canvas, die --success-*-Familie), folgen demselben Muster aus name und name-foreground. Ein Projekt, das schon shadcn-Tokens hat, kann unsere übernehmen, ohne etwas umzubenennen.

    Wann brauchst du Tailwind v4 @theme inline?

    Nimm @theme inline immer dann, wenn eine Theme-Variable auf eine andere Variable zeigt, und das gilt für jedes Token, das sich mit dem Theme ändert. Die Tailwind-Doku (opens in a new tab) sagt es direkt: Mit inline nutzt die Utility-Klasse den Wert der Theme-Variable, statt auf die Theme-Variable selbst zu verweisen. So sieht das im kompilierten CSS aus:

    css
    /* @theme inline { --color-surface: var(--surface); } */
    .bg-surface {
    background-color: var(--surface);
    }
    /* @theme { --color-surface: var(--surface); } */
    :root, :host {
    --color-surface: var(--surface);
    }
    .bg-surface {
    background-color: var(--color-surface);
    }

    Der Unterschied zeigt sich unterhalb des Root-Elements. Eine Custom Property erbt ihren berechneten Wert, und var() wird dort eingesetzt, wo die Property deklariert ist. In der zweiten Version wird --color-surface einmal aufgelöst, auf :root, und jedes Element erbt dieses Ergebnis. Eine .dark-Klasse auf <html> funktioniert trotzdem, denn <html> ist das Root-Element. Eine dunkle Section in einer hellen Seite (<section className="dark">), ein dunkler Vorschaubereich oder ein data-theme-Attribut auf einem Container funktionieren nicht: Die Section erbt den hellen Wert, den :root schon aufgelöst hat. Mit inline liest bg-surface var(--surface) auf dem Element selbst und übernimmt so den nächstgelegenen Override. Die Tailwind-Doku erklärt die Option mit einem ganz ähnlichen Beispiel aus Eltern- und Kindelement.

    Einen Haken hat inline noch: Wenn du selbst var(--color-surface) schreibst, in einem Arbitrary Value wie bg-[var(--color-surface)] oder in normalem CSS, gibt Tailwind 4.3 dafür --color-surface: var(--surface) auf :root aus, und diese Referenz wird wieder auf dem Root-Element aufgelöst. Lies in Komponenten deshalb die Rohvariable: bg-(--surface) oder var(--surface).

    Zwei weitere Regeln aus der Doku: Theme-Variablen müssen auf oberster Ebene stehen, nie unter einem Selektor oder einer Media Query, also landen Werte pro Theme ohnehin außerhalb von @theme; und Tailwind gibt nur die Theme-Variablen aus, die dein Code nutzt, außer du schreibst @theme static. Die Rohvariablen in :root werden immer ausgeliefert, was hilft, wenn eine Chart-Bibliothek ein Token mit getComputedStyle liest.

    Welche semantischen Farb-Tokens braucht eine Komponentenbibliothek?

    Benenn Farben nach ihrer Rolle, nie nach dem Farbton. Eine Komponentenbibliothek braucht Flächen in der Reihenfolge, in der sie sich stapeln, Textstufen mit einer Kontrast-Untergrenze, transparente Tints für Hover und Füllungen, einen Rahmen und einen Fokusring sowie Status-Tones. Semantische Farb-Tokens sorgen dafür, dass Komponenten nie bg-gray-100 oder text-blue-600 enthalten, und ein Theme kann jeden Wert ändern, ohne eine Komponente anzufassen.

    Flächen, von hinten nach vorn

    TokenLightDarkEinsatz
    background#FFFFFF#000000die Seite
    canvas#F6F7F9#0E0E10die Seite einer App (Dashboards, Admin), unter weißen Panels
    card#EBEBEB#121212flache graue Kacheln ohne Schatten
    surface#FFFFFF mit shadow-raised#1A1A1Aerhöhte Panels: Listengruppen, Stat-Cards, Formulare
    popover#FFFFFF mit shadow-floating#262626alles, was schwebt: Menüs, Selects, Dialoge, Sheets, Toasts
    primary#000000#FAFAFAdie neutrale volle Füllung und Tooltips

    Im Dark Mode werden die Flächen heller, je weiter sie nach vorn kommen: #000, #121212, #1A1A1A, #262626. Im Light Mode sind die meisten weiß, also übernimmt der Schatten die Trennung. canvas gibt es, weil ein Dashboard auf einer weißen Seite nichts hat, wovon sich weiße Cards abheben könnten; eine kühle graue Seite sorgt dafür, dass jedes bg-surface-Panel als Card wirkt.

    Textstufen

    StufeKlasseEinsatzNiedrigstes Verhältnis in unseren Token-Paaren
    Primärtext-foregroundTitel, Werte, Labels10.60:1 (helle Card)
    Sekundärtext-muted-foregroundBeschreibungen, Hilfetext, Zeitstempel4.62:1 (helle Card)
    Deaktivierttext-foreground/45nur deaktivierte Labelskeine Vorgabe
    Tone-Texttext-success-soft-foreground und verwandte TokensFehler, Deltas, Links5.28:1 (Brand auf der hellen Card)

    text-muted-foreground ist die Untergrenze für alles, was Menschen lesen müssen. Die deaktivierte Stufe hat kein Kontrastziel, weil WCAG 2.2 Erfolgskriterium 1.4.3 (opens in a new tab) Text in inaktiven Controls ausnimmt, und deshalb wird sie nie für etwas anderes genutzt.

    Tints statt grauer Füllungen

    Hover-States, Feldfüllungen, Chips, Tracks und Skeletons nutzen transparenten Foreground: bg-foreground/5, /7, /10 und /15, dazu border-foreground/10 für Haarlinien. Tailwind 4.3 kompiliert den Opacity-Modifier zu color-mix(), mit der reinen Farbe als Fallback:

    css
    .bg-foreground\/5 {
    background-color: var(--foreground);
    @supports (color: color-mix(in lab, red, red)) {
    background-color: color-mix(in oklab, var(--foreground) 5%, transparent);
    }
    }

    Ein Tint bezieht sich auf das, was hinter ihm liegt, also rückt er auf jeder Fläche und in beiden Themes eine Stufe in Richtung Textfarbe. Eine feste Füllung kann das nicht. Unser dunkles --muted ist #171717: heller als die schwarze Seite und dunkler als die #1A1A1A-Surface, dasselbe Feld würde also auf der einen erhaben und auf der anderen vertieft wirken. Der Fallback in voller Stärke erscheint nur in Browsern ohne color-mix(), und die liegen alle unter Tailwinds Browser-Baseline (opens in a new tab) von Chrome 111, Safari 16.4 und Firefox 128.

    secondary und accent von shadcn/ui bleiben in unserer Datei, damit portierte Komponenten weiter funktionieren, aber unser --accent ist ein oranger Tint, und neue Komponenten nutzen keins von beiden.

    Wie funktionieren Statusfarben ohne eine Klasse pro Farbe?

    Gib jedem Status-Tone vier Tokens: eine volle Füllung, den Text auf dieser Füllung, eine weiche getönte Fläche und Tone-Text, der auf der weichen Fläche und auf jeder neutralen Fläche lesbar ist. Dann lass Komponenten ihre Farben aus fünf generischen Variablen beziehen, die eine einzige Klasse setzt. Das ist die ganze Idee hinter dem kostenlosen Tones-Utility.

    Die vier Tokens pro Tone, für Brand, Success, Warning, Destructive und Info:

    css
    --success: #15803d; /* bg-success: Buttons, volle Badges, Fortschritt */
    --success-foreground: #ffffff; /* Text auf der Füllung */
    --success-soft: #e7f6ec; /* bg-success-soft: weiche Badges, Alerts, ausgewählte Zeilen */
    --success-soft-foreground: #166534; /* farbiger Text auf dem Tint und auf jeder neutralen Fläche */

    Die Trennung zwischen Füllung und Tone-Text ist die Regel, die man am leichtesten überspringt, und genau dort bricht der Dark Mode. Füllungen sind darauf abgestimmt, weißen Text zu tragen, und als Text taugen sie wenig. text-destructive (#DC2626) auf unserem dunklen Popover kommt auf 3.13:1; text-destructive-soft-foreground (#FF8A80) auf derselben Fläche auf 6.63:1. Die Warning-Füllung ist Amber (#F5A524) mit 2.04:1 gegen Weiß, also trägt sie dunklen Text (#2B1700, 8.40:1) und wird nie als Text auf einer neutralen Fläche genutzt.

    Komponenten-Variablen

    Eine Komponente mit einem tone-Prop mappt Tokens nicht selbst. toneClasses[tone] setzt fünf Variablen, und jede Variante färbt sich damit ein:

    ts
    // lib/tones.ts (Auszug)
    success:
    "[--tone:var(--success)] [--tone-foreground:var(--success-foreground)] [--tone-soft:var(--success-soft)] [--tone-soft-foreground:var(--success-soft-foreground)] [--tone-border:color-mix(in_oklab,var(--success)_45%,transparent)]",

    Der neutrale Tone mappt --tone auf --primary (schwarz im Light, fast weiß im Dark) und baut seine weiche Fläche aus color-mix(in oklab, var(--foreground) 7%, transparent), deshalb funktioniert ein neutraler Soft-Button auf jeder Fläche. Ein Badge nach diesem Muster:

    tsx
    import type { ComponentProps } from "react";
    import { colorVars, toneClasses, type Tone } from "@/lib/tones";
    import { cn } from "@/lib/utils";
    type StatusBadgeProps = ComponentProps<"span"> & { tone?: Tone; color?: string };
    export function StatusBadge({ tone = "neutral", color, className, style, ...props }: StatusBadgeProps) {
    return (
    <span
    data-slot="status-badge"
    className={cn(
    toneClasses[tone],
    "inline-flex rounded-full bg-(--tone-soft) px-2 py-0.5 text-xs font-medium text-(--tone-soft-foreground)",
    className,
    )}
    style={{ ...colorVars(color), ...style }}
    {...props}
    />
    );
    }

    bg-(--tone-soft) ist die Kurzschreibweise von Tailwind v4 für bg-[var(--tone-soft)]. Das kostenlose Badge ist die vollständige Version dieses Musters, mit den Varianten Solid, Soft und Outline. colorVars(color) baut dieselben fünf Variablen aus einer beliebigen CSS-Farbe als Inline-Style. Der Wert für Text auf der Füllung nutzt Relative Color Syntax, oklch(from <color> clamp(0, (0.7 - l) * 1000, 1) 0 0): weiß, wenn die OKLCH-Helligkeit der Farbe unter 0.7 liegt, schwarz darüber. Stand Oktober 2026 listet caniuse (opens in a new tab) volle Unterstützung für Relative Color Syntax ab Chrome 131, Safari 18 und Firefox 133 und davor nur teilweise Unterstützung; volle Unterstützung kam also später als Tailwinds eigene Baseline. Wo ein Browser den Wert nicht berechnen kann, ist die Deklaration zum Zeitpunkt der Wertberechnung ungültig, und der Text erbt die Farbe seines Elternelements. Wenn du diese Browser unterstützt, erzwing die Textfarbe mit className="[--tone-foreground:white]".

    Jede Zeile zeigt einen Tone auf drei Arten, gebaut aus denselben fünf Variablen, und die letzte Zeile erzeugt colorVars aus einem einzigen Hex-Wert, #0EA5E9. Wechsel das Theme der Seite: Die vollen Füllungen bleiben, die weichen Tints und der Tone-Text nehmen ihre dunklen Werte an
    $ npx wingo-ui@latest add tones
    FreeDoku: Tones

    Der kostenlose Button ist das Referenzbeispiel für die Nutzung. Seine Varianten-Map enthält überhaupt keine Farbe:

    ts
    const VARIANTS: Record<ButtonVariant, string> = {
    solid: "bg-(--tone) text-(--tone-foreground) shadow-raised",
    soft: "bg-(--tone-soft) text-(--tone-soft-foreground)",
    outline: "border border-(--tone-border) text-(--tone-soft-foreground)",
    ghost: "text-(--tone-soft-foreground)",
    link: "rounded-[4px] text-(--tone-soft-foreground) underline decoration-current/35 decoration-1 underline-offset-4 transition-[color,text-decoration-color] hover:decoration-current",
    };

    Sechs Tones mal fünf Varianten, plus jede eigene Farbe, aus fünf Zeilen. Hover und Press sind eine Schicht aus currentColor mit 8% und 12% Deckkraft, also funktionieren auch sie für jeden Tone ohne zusätzliche Klassen.

    Wie gestaltest du Dark Mode mit Tokens?

    Behandle Dark als zweiten Satz Werte für dieselben Rollen, von Hand gewählt, und schalte ihn mit einer Klasse um. Komponenten behalten in beiden Themes dieselben Klassen, also braucht fast nichts in einer Komponente ein dark:-Präfix.

    Die Variante: Unsere Tokens nutzen, wie die von shadcn/ui, @custom-variant dark (&:is(.dark *));. Die Tailwind-Doku zum Dark Mode (opens in a new tab) zeigt &:where(.dark, .dark *), das auch das Element selbst trifft, das die Klasse trägt, und keine Spezifität hinzufügt. Mit der Klasse auf <html> verhalten sich beide im normalen Einsatz gleich. Die Klasse selbst kommt von next-themes (opens in a new tab), das sie vor dem ersten Paint schreibt: Pack die App in seinen ThemeProvider mit attribute="class" (die vollständige Provider-Datei steht weiter unten im Abschnitt zu Props) und rendere im Root-Layout <html lang="en" suppressHydrationWarning>, denn next-themes ändert die Klasse, bevor React hydriert:

    tsx
    // app/layout.tsx
    import type { ReactNode } from "react";
    import { Providers } from "./providers";
    import "./globals.css";
    export default function RootLayout({ children }: { children: ReactNode }) {
    return (
    <html lang="en" suppressHydrationWarning>
    <body className="bg-background text-foreground">
    <Providers>{children}</Providers>
    </body>
    </html>
    );
    }

    Zwei Posts vertiefen das Umschalten selbst: Tailwind v4 Dark Mode behandelt die @custom-variant-Zeile, den Toggle und warum dark: sie ignorieren kann, und der Fix für den next-themes Hydration Error entfernt das Aufblitzen des Themes und rendert einen Toggle, der schon beim ersten Paint stimmt.

    Wie ein gestaltetes Dark Theme in der Praxis aussieht, aus unserer Token-Datei:

    • Flächen werden heller, je weiter sie nach vorn kommen. #000 Seite, #121212 Card, #1A1A1A Surface, #262626 Popover.
    • Füllungen bleiben, weiche Flächen wechseln. Die Brand-Füllung ist in beiden Themes #D63F00, weißer Text behält also 4.59:1. Ihre weiche Fläche wechselt von #FFEFE6 zu #2A1509 und ihr Text von #B03400 zu #FF8A4C.
    • Die neutrale Füllung kehrt sich um. primary ist im Light Mode schwarz mit weißem Text und im Dark Mode #FAFAFA mit #171717, also behält der neutrale volle Button 21:1 im Light und 17.16:1 im Dark.
    • Elevation wechselt die Technik. Ein Schlagschatten ist auf Schwarz kaum zu sehen, also setzen dunkle Schatten auf eine beleuchtete Oberkante und eine weiße Haarlinie (siehe die Elevation-Stufen unten).
    • Der Fokusring verschiebt sich. --ring ist oklch(0.60 0.20 36.93) im Light und oklch(0.6617 0.2215 36.93) im Dark, auf Weiß also dunkler, damit eine 2px-Outline auf Weiß und auf der grauen Card 3:1 hält.
    • Manche Komponenten brauchen eine Ausnahme. Unsere weiche Card nutzt im Light bg-card, im Dark aber bg-foreground/7, weil die dunkle --card dasselbe Grau hat wie eine dunkle Sidebar oder Stage und die Card dort verschwinden würde. Ein Skript, das Farben invertiert, trifft diese Entscheidung nie.

    Wie prüfst du den Kontrast für jedes Token-Paar?

    Liste die Paare auf, die zusammen genutzt werden sollen, rechne beide Farben nach sRGB um, berechne das WCAG-Verhältnis in beiden Themes und lass den Build unter 4.5:1 scheitern. WCAG verlangt 4.5:1 für Fließtext und 3:1 für großen Text (1.4.3 (opens in a new tab)) sowie 3:1 für UI-Komponenten und Fokusindikatoren gegenüber dem, was daneben liegt (1.4.11 (opens in a new tab)).

    Unser Check deckt 54 Paare pro Theme ab, insgesamt 108, und meldet Stand Release 1.7.0 kein Paar unter 4.5:1. Die knappsten Paare sind brand-foreground auf brand mit 4.59:1 und muted-foreground auf der hellen card mit 4.62:1. Hier ist eine eigenständige Version, die du in scripts/ legen und mit npx tsx scripts/check-contrast.ts ausführen kannst. Sie liest Hex- und oklch()-Werte (Helligkeit als Dezimalzahl oder Prozent), folgt var()-Referenzen und beendet sich bei einem Fehlschlag mit Code 1. Sie erwartet die Token-Namen aus diesem Guide; wenn deine anders heißen, pass SURFACES, TONES und PAIRS an:

    ts
    // scripts/check-contrast.ts
    import { readFileSync } from "node:fs";
    type Rgb = [number, number, number];
    const css = readFileSync("app/globals.css", "utf8").replace(/\/\*[\s\S]*?\*\//g, "");
    // jedes --name: value in den Blöcken, die mit diesem Selektor beginnen
    function vars(selector: string) {
    const map = new Map<string, string>();
    const block = new RegExp(`(?:^|\\n)${selector.replace(".", "\\.")}\\s*\\{([^}]*)\\}`, "g");
    for (const m of css.matchAll(block))
    for (const d of m[1].matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g)) map.set(d[1], d[2].trim());
    return map;
    }
    export function oklchToRgb(l: number, c: number, h: number): Rgb {
    const a = c * Math.cos((h * Math.PI) / 180);
    const b = c * Math.sin((h * Math.PI) / 180);
    const l3 = (l + 0.3963377774 * a + 0.2158037573 * b) ** 3;
    const m3 = (l - 0.1055613458 * a - 0.0638541728 * b) ** 3;
    const s3 = (l - 0.0894841775 * a - 1.291485548 * b) ** 3;
    const linear = [
    4.0767416621 * l3 - 3.3077115913 * m3 + 0.2309699292 * s3,
    -1.2684380046 * l3 + 2.6097574011 * m3 - 0.3413193965 * s3,
    -0.0041960863 * l3 - 0.7034186147 * m3 + 1.707614701 * s3,
    ];
    // in sRGB begrenzen, dann gamma-kodieren
    return linear.map((v) => {
    const x = Math.min(1, Math.max(0, v));
    return x <= 0.0031308 ? 12.92 * x : 1.055 * x ** (1 / 2.4) - 0.055;
    }) as Rgb;
    }
    function parse(value: string, map: Map<string, string>): Rgb {
    const ref = value.match(/^var\((--[\w-]+)\)$/);
    if (ref) return parse(map.get(ref[1]) ?? "", map);
    if (value.startsWith("#")) {
    const hex = value.length === 4 ? [...value.slice(1)].map((ch) => ch + ch).join("") : value.slice(1);
    return [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255) as Rgb;
    }
    const ok = value.match(/^oklch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)\s*\)$/);
    if (ok) return oklchToRgb(ok[2] ? +ok[1] / 100 : +ok[1], +ok[3], +ok[4]);
    throw new Error(`"${value}" lässt sich nicht parsen`);
    }
    const channel = (c: number) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
    const luminance = ([r, g, b]: Rgb) => 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
    export function contrast(fg: Rgb, bg: Rgb) {
    const [hi, lo] = [luminance(fg), luminance(bg)].sort((x, y) => y - x);
    return (hi + 0.05) / (lo + 0.05);
    }
    const SURFACES = ["background", "card", "popover", "surface", "canvas"];
    const TONES = ["brand", "success", "warning", "info", "destructive"];
    const PAIRS = [
    ["foreground", "background"],
    ...SURFACES.map((s) => ["muted-foreground", s]),
    ...TONES.flatMap((t) => [
    [`${t}-foreground`, t],
    [`${t}-soft-foreground`, `${t}-soft`],
    ...SURFACES.map((s) => [`${t}-soft-foreground`, s]),
    ]),
    ];
    const light = vars(":root");
    const themes = { light, dark: new Map([...light, ...vars(".dark")]) };
    let failures = 0;
    for (const [theme, map] of Object.entries(themes)) {
    for (const [fg, bg] of PAIRS) {
    const ratio = contrast(parse(map.get(`--${fg}`) ?? "", map), parse(map.get(`--${bg}`) ?? "", map));
    if (ratio < 4.5) {
    failures++;
    console.log(`FEHLER ${theme}: ${fg} auf ${bg} hat ${ratio.toFixed(2)}:1`);
    }
    }
    }
    console.log(`${PAIRS.length * 2} Paare geprüft, ${failures} unter 4.5:1`);
    process.exitCode = failures ? 1 : 0;

    Diese gekürzte Version prüft 82 Paare. Unser vollständiges Skript ergänzt die übrigen Paare für Flächen, Primary und Legacy-Tokens und verrechnet transparente Werte vorher mit der Seite. Der wertvolle Teil ist die Paarliste: Sie hält fest, welcher Text auf welcher Fläche stehen darf, also wird ein neues Token an dem Tag geprüft, an dem es dazukommt. Lass es in CI neben dem Type-Check laufen.

    Solltest du Tailwind v4 OKLCH-Werte oder Hex schreiben?

    Nimm das Format, mit dem du am besten arbeiten kannst; das Format ändert den Kontrast nicht. Tailwinds eigene Palette ist in v4.0 von rgb auf oklch umgestiegen, um den größeren Farbraum zu nutzen (Release-Post (opens in a new tab)), und die Farbreferenz (opens in a new tab) listet jeden Tailwind v4 OKLCH-Wert, etwa --color-red-500: oklch(63.7% 0.237 25.331). OKLCH verdient seinen Platz, wenn du Farben im Code ableitest, denn die Helligkeit ist ein eigener Kanal: Du kannst sie verschieben, während Hue und Chroma bleiben, und das Ergebnis sieht immer noch nach derselben Farbe aus.

    In unserer Datei sind Neutraltöne oklch() mit Chroma null (oklch(0.3211 0 0) ist der helle Foreground), und die Tone-Werte sind Hex, von Hand gegen den Kontrast-Check abgestimmt. Der Theme-Generator geht den umgekehrten Weg: Du wählst für jeden Tone eine Grundfarbe (Brand, Success, Warning, Danger, Info), und er leitet daraus Füllung, weiche Fläche und Text des Tones in OKLCH ab. Für den Light Mode setzt er die weiche Fläche auf Helligkeit 0.965 mit Chroma höchstens 0.035 und senkt dann die Helligkeit des Tone-Texts ab 0.6 in Schritten von 0.005, bis er 4.6:1 auf der weichen Fläche und auf jeder neutralen Fläche erreicht. Verschieb oklchToRgb und contrast aus dem Kontrast-Skript in ein eigenes Modul, scripts/color.ts, damit der Import den Check nicht startet, und dieselbe Suche passt in ein paar Zeilen:

    ts
    import { contrast, oklchToRgb } from "./color";
    type Rgb = [number, number, number];
    // Hue und Chroma behalten, Helligkeit verschieben, bis die Farbe auf jeder möglichen Fläche lesbar ist
    export function findReadable(c: number, h: number, surfaces: Rgb[], from: number, to: number, target = 4.6): Rgb {
    const step = from < to ? 0.005 : -0.005;
    for (let l = from; step > 0 ? l <= to : l >= to; l += step) {
    const rgb = oklchToRgb(l, c, h);
    if (Math.min(...surfaces.map((bg) => contrast(rgb, bg))) >= target) return rgb;
    }
    return oklchToRgb(to, c, h);
    }

    Das Ziel ist 4.6 statt 4.5, damit das Runden auf Hex eine Farbe nicht unter die Grenze drücken kann. Eine Warnung noch: Ein OKLCH-Wert mit hoher Chroma kann außerhalb von sRGB liegen, und was ein Bildschirm dann zeigt, hängt von Display und Browser ab. Halte Token-Werte innerhalb von sRGB oder miss zumindest den begrenzten sRGB-Wert, so wie oklchToRgb es tut.

    Wie werden Radius, Elevation, z-index und Motion zu Tokens?

    Sie werden zu Skalen mit einer festen Zahl von Stufen, jede mit genau einer Aufgabe, sodass eine Komponente eine Stufe wählt statt einer Zahl. Ein Tailwind v4 Design System, das nur Farben tokenisiert, landet trotzdem bei Schatten-Einzelfällen und irgendwo bei einem z-[9999].

    Der Radius folgt der Größe

    Koppel den Radius an die Größe des Controls. Ein 32px- und ein 48px-Button wirken nur dann wie dieselbe Form, wenn der größere den größeren Radius hat. Unsere Controls übernehmen diese Map vom Button:

    ts
    type Scale = "sm" | "md" | "lg";
    type ButtonRadius = "none" | "sm" | "md" | "lg" | "full";
    const RADII: Record<ButtonRadius, Record<Scale, string>> = {
    none: { sm: "rounded-none", md: "rounded-none", lg: "rounded-none" },
    sm: { sm: "rounded-[6px]", md: "rounded-[8px]", lg: "rounded-[10px]" },
    md: { sm: "rounded-[8px]", md: "rounded-[10px]", lg: "rounded-[12px]" },
    lg: { sm: "rounded-[10px]", md: "rounded-[14px]", lg: "rounded-[16px]" },
    full: { sm: "rounded-full", md: "rounded-full", lg: "rounded-full" },
    };

    Flächen nutzen rounded-2xl (16px) für Cards, Menüs und Popovers, rounded-3xl (24px) für Dialoge und rounded-t-[28px] für Bottom Sheets. Alles innerhalb einer Fläche mit Padding bekommt den äußeren Radius minus Padding: Ein Menü mit rounded-2xl p-1.5 hat Items mit rounded-[10px]. Größen folgen derselben Logik: 32, 40 und 48px, wobei die 40px-Größe auf Touchscreens auf 44px wächst, die Regel aus unserem Guide zu Mobile-first React-Komponenten.

    Die Falle ist ein globales Radius-Token. Unsere Datei trägt für portierte Komponenten noch die ältere Skala von shadcn/ui: --radius: 0.375rem, --radius-lg: var(--radius) und --radius-sm: calc(var(--radius) - 4px). Das biegt Tailwinds rounded-sm, rounded-md, rounded-lg und rounded-xl überall auf 2, 4, 6 und 10px um, auch in Markup, das du aus der Tailwind-Doku kopierst (das kompilierte .rounded-lg ist border-radius: var(--radius)), deshalb nutzen neue Komponenten diese vier Klassen nie. Stand Oktober 2026 leitet die Theming-Doku von shadcn/ui (opens in a new tab) die Skala stattdessen per Multiplikation ab (--radius-sm: calc(var(--radius) * 0.6) bis --radius-4xl), was dieselbe projektweite Wirkung hat. Wenn du den Radius global themst, musst du jede Komponente kontrollieren, die ihn nutzt.

    Drei Elevation-Stufen

    Drei Stufen reichen für eine Bibliothek: shadow-raised für Buttons, Cards und Thumbs, shadow-floating für Popovers, Menüs und Toasts, shadow-modal für Dialoge und Sheets. Jede Stufe bringt ihre eigene 1px-Haarlinie mit, eine erhöhte Fläche braucht also keinen Rahmen:

    css
    :root {
    --elevation-floating: 0 0 0 1px rgb(0 0 0 / 0.06), 0 4px 12px -2px rgb(0 0 0 / 0.08), 0 16px 36px -10px rgb(0 0 0 / 0.16);
    }
    .dark {
    /* auf einer schwarzen Seite ist ein Schlagschatten kaum sichtbar: eine beleuchtete Oberkante und eine Haarlinie übernehmen */
    --elevation-floating: inset 0 1px 0 0 rgb(255 255 255 / 0.07), 0 0 0 1px rgb(255 255 255 / 0.09), 0 8px 24px -4px rgb(0 0 0 / 0.6), 0 20px 48px -12px rgb(0 0 0 / 0.7);
    }
    @theme inline {
    --shadow-floating: var(--elevation-floating);
    }

    Eine interaktive Card wechselt beim Hover zwischen zwei dieser Stufen, und das Anheben läuft als CSS-Transition auf einer Spring-Kurve (siehe unten), also animiert ein Raster aus fünfzig Cards ohne JavaScript.

    Fahr mit der Maus über die Card, um zu sehen, wie sie sich von shadow-raised auf shadow-floating hebt, und drück sie dann, um das Eindrücken auf 98% zu sehen (Smartphones bekommen nur das Drücken). Wechsel das Theme der Seite, um zu sehen, wie die dunkle Elevation stattdessen eine beleuchtete Oberkante zeichnet
    $ npx wingo-ui@latest add card
    FreeDoku: Card

    Stapelebenen

    KlasseWertEinsatz
    z-dropdown1000Panels im Fluss, die nicht in ein Portal gerendert werden
    z-sticky1100Sticky Header, Tab-Bars, CTA-Leisten
    z-overlay1200der abgedunkelte Hintergrund
    z-modal1300Dialoge, Sheets, Drawer
    z-popover1400Menüs, Selects und Popovers im Portal
    z-toast1500Toasts
    z-tooltip1600Tooltips, immer ganz oben

    Popovers liegen absichtlich über Modals: Ein Select in einem Dialog muss sich darüber öffnen. Kein Overlay in der Bibliothek nutzt eine rohe Zahl wie z-50; kleine Werte wie z-10 ordnen nur Teile innerhalb einer Komponente.

    Motion

    CSS-Transitions nutzen drei Dauern (duration-fast 150ms, duration-normal 240ms, duration-slow 400ms) und benannte Kurven (ease-smooth ist cubic-bezier(0.22, 1, 0.36, 1), ease-sheet ist cubic-bezier(0.32, 0.72, 0, 1)). Unsere JavaScript-Springs (die vier Framer Motion Spring-Presets) werden außerdem in CSS-linear()-Easings abgetastet: ease-spring mit duration-spring (500ms) entspricht einer Spring mit Stiffness 300 und Damping 30. Wenn das Betriebssystem reduzierte Bewegung anfordert, sollten diese Springs kurzen Fades weichen; unser Guide zu prefers-reduced-motion in React setzt das in Next.js ohne Hydration Mismatch um.

    Können Komponenten-Props wie Design Tokens funktionieren?

    Ja, für die Entscheidungen, die Props sind: der Radius jedes Buttons, die Standardgröße von Feldern, die Variante jeder Card. CSS-Variablen können kein Prop setzen, also liest Wingo UI diese Werte aus einem React-Provider, und UIProvider defaults wird zur Token-Schicht für Props. Der kostenlose UIProvider legt app-weite Defaults unter die Props, die jede Komponente bekommt, geschlüsselt nach ihrem data-slot-Namen:

    tsx
    // app/providers.tsx
    "use client";
    import type { ReactNode } from "react";
    import { ThemeProvider } from "next-themes";
    import { UIProvider } from "@/lib/ui-config";
    export function Providers({ children }: { children: ReactNode }) {
    return (
    <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
    <UIProvider locale="en-US" defaults={{ button: { radius: "full" }, card: { variant: "outline" } }}>
    {children}
    </UIProvider>
    </ThemeProvider>
    );
    }

    In der Komponente liest die erste Zeile die Defaults, und die Defaults in der Signatur bleiben der letzte Fallback:

    tsx
    export function Button(buttonProps: ButtonProps) {
    const { variant = "solid", tone = "neutral", size = "md", radius = "md", ...rest } = useDefaults("button", buttonProps);
    // ...
    }

    Explizite Props gewinnen, dann der Provider, dann die eigenen Defaults der Komponente. Ein undefined-Prop überschreibt keinen Provider-Wert, also können Wrapper Props durchreichen, ohne das Theme zu löschen. Dieselbe Signatur nimmt ref als normales Prop, ohne forwardRef-Wrapper; unser Migrationsguide zu forwardRef in React 19 bringt ältere Komponenten auf dieses Muster, TypeScript-Typen inklusive.

    Der erste Button nutzt seine eingebauten Defaults; die beiden darunter bekommen radius full, size lg und tone brand von einem UIProvider, und der Outline-Button behält die Variante, die er selbst übergibt
    $ npx wingo-ui@latest add ui-config
    FreeDoku: UIProvider

    Wie bringst du diese Tokens in ein bestehendes Next.js-Projekt?

    Führ npx wingo-ui@latest init in einem Projekt mit Tailwind CSS v4 aus. Der Befehl schreibt wingo-ui.json und lib/utils.ts (den cn-Helper), installiert clsx und tailwind-merge und führt die Tokens in das Stylesheet zusammen, das @import "tailwindcss" enthält. Er ergänzt nur fehlende Namen und behält deine Werte; add bringt später alle Tokens mit, die eine neue Komponente braucht. Der komplette Block, 74 Variablen in :root plus ihre dunklen Werte und Tailwind-Mappings, liegt unter /r/tokens.css, und Coding-Agents bekommen dasselbe CSS über das MCP-Tool get_theme_tokens (siehe unseren MCP-Guide für Komponentenbibliotheken). Die Theming-Doku zeigt die wichtigsten Tokens mit ihren hellen und dunklen Werten und die zwei Dark-Mode-Setups, die die CLI unterstützt: eine .dark-Klasse oder prefers-color-scheme, wenn dein Stylesheet das schon nutzt.

    Zum Umgestalten öffnest du den Theme-Generator, wählst eine Markenfarbe, Neutraltöne, Radius, Schrift und Komponenten-Defaults, prüfst seinen Kontrastbericht und wendest den Export an:

    bash
    npx wingo-ui@latest theme apply ./theme.json

    Der Befehl ersetzt die Werte der Tokens, die das Theme setzt, in Light und Dark, ergänzt Tailwind-Mappings für neue Tokens und gibt die UIProvider-Defaults und die Locale aus, die du in deinen Provider einfügst.

    Wenn du von shadcn/ui kommst: Die gemeinsamen Namen passen zusammen, aber zwei haben eine andere Bedeutung. Unsere --card ist eine flache graue Kachel, während sie bei shadcn der Hintergrund der Card ist, und unser --accent ist ein oranger Tint. Weil init deine bestehenden Werte behält, behält ein shadcn-Projekt seine eigene --card und seinen eigenen --accent, prüf also nach dem Zusammenführen die weiche Card-Variante. Unser Vergleich der shadcn-Alternativen behandelt die größeren Unterschiede zwischen den beiden Bibliotheken.

    Welche Fehler machen ein Tailwind v4 Design System kaputt?

    Dieselben paar Fehler tauchen in den meisten Token-Dateien auf:

    • dark:-Klassen in Komponenten. Einer Komponente, die dark:bg-zinc-900 braucht, fehlt ein Token.
    • Palettennamen in Komponenten. bg-blue-600 kann keinem Theme folgen; nimm eine Rolle oder einen Tone.
    • Füllfarben als Text. text-destructive auf einer dunklen Fläche fällt bei AA durch; nimm den Soft-Foreground.
    • Werte pro Theme in einfachem @theme. Halte sie in :root und .dark, gemappt mit inline.
    • Ein globaler Radius, der Tailwinds Skala umschreibt. Jedes eingefügte rounded-lg ändert sich mit.
    • Rohe z-index-Zahlen. Zwei z-50-Overlays von zwei Autoren kollidieren.
    • Kein Kontrast-Check. Tokens driften mit jeder Anpassung; ein Check in CI fängt das noch am selben Tag ab.

    Wo solltest du anfangen?

    Führ npx wingo-ui@latest init in deinem Next.js-Projekt aus, dann npx wingo-ui@latest add button, das die Items Tones und UIProvider gleich mitbringt. Ersetz in einem echten Screen eine fest verdrahtete Farbe durch ein Rollen-Token, öffne ihn in beiden Themes und nimm das Kontrast-Skript in CI auf. Zwei weitere Guides in Next.js und Tailwind CSS behandeln den Komponentencode rund um die Tokens: die Migration von forwardRef in React 19 und einen SSR-sicheren React use mobile Hook, der ohne Mismatch hydriert. Für das Projekt drumherum folgst du dem Setup einer Next.js-Komponentenbibliothek für den App Router oder dem shadcn Vite Setup für eine Vite-App. Wenn du auf den Tokens eigene Komponenten schreibst, zeigt der Guide zum React Controllable State Hook, wie ein einziges value-Prop kontrolliert und unkontrolliert funktioniert. Für die Seiten, die sie ausliefern, baut Next.js JSON-LD typisierte strukturierte Daten, die zur Seite passen, und Next.js Cookie Consent ergänzt ein DSGVO-Banner mit Google Consent Mode v2.

    Komponenten in diesem Artikel

    • Button

      The React button component every screen starts with: five variants, six tones, all sizes, and a loading state that never jumps.

      Free
    • Tones

      One way to color a React component: a tone or any CSS color becomes five CSS variables every variant paints with.

      Free
    • UIProvider

      A React config provider for Wingo UI: app-wide default props for every component, the Intl locale and a reduced-motion policy.

      Free
    • Card

      A React card component for listings, stats, settings and plans: media, header, body and footer parts, and a lift-and-press mode.

      Free
    • Badge

      A React badge for status chips, removable filters and animated counts in three variants and six tones, plus a count or dot on any corner.

      Free

    Häufige Fragen

    Wohin gehören Design Tokens in Tailwind v4?

    In die globale CSS-Datei, nach @import "tailwindcss". Variablen in einem @theme-Block werden zu Utilities (--color-surface ergibt bg-surface), und Werte, die sich pro Theme ändern, stehen in :root und .dark und werden mit @theme inline gemappt. Eine tailwind.config.js brauchst du nicht mehr.

    Was ist der Unterschied zwischen @theme und @theme inline in Tailwind v4?

    Mit @theme verweist die Utility auf die Theme-Variable, bg-surface wird also zu var(--color-surface) kompiliert, und das wird einmal auf :root aufgelöst. Mit @theme inline bekommt die Utility den referenzierten Wert direkt, bg-surface wird zu var(--surface) und löst sich auf dem Element auf, das die Klasse nutzt. Nimm inline immer dann, wenn eine Theme-Variable auf eine andere Variable zeigt.

    Sollten Farben in Tailwind v4 OKLCH oder Hex sein?

    Beides funktioniert, denn Tokens sind ganz normale CSS-Farben, und das Format ändert den Kontrast nicht. Tailwinds eigene Palette ist mit v4.0 auf oklch umgestiegen. OKLCH hilft vor allem, wenn du Farben im Code ableitest, weil du Hue und Chroma behältst und nur die Helligkeit verschiebst, bis ein Kontrastziel erreicht ist.

    Wie bekommen Tailwind v4 Design Tokens einen Dark Mode?

    Definier dieselben Variablen in einem .dark-Block neu und ergänze @custom-variant dark (&:is(.dark *));, damit auch dark:-Utilities der Klasse folgen. next-themes mit attribute="class" setzt die Klasse vor dem ersten Paint. Komponenten mit semantischen Tokens brauchen dann keine dark:-Klassen.

    Wie prüfe ich den Farbkontrast von Design Tokens?

    Liste jedes Text-Token mit jeder Fläche auf, auf der es stehen kann, rechne die Werte nach sRGB um und berechne das WCAG-Verhältnis für Light und Dark. Lass den Build unter 4.5:1 für Fließtext scheitern; Fokusringe und andere UI-Formen brauchen 3:1.

    Sind die Design Tokens von Wingo UI kostenlos?

    Ja. npx wingo-ui@latest init führt die Tokens ohne Account in dein Stylesheet zusammen, und die Items Button, Card, Tones und UIProvider aus diesem Guide sind kostenlos. Stand Release 1.7.0 sind 75 der 326 Registry-Items kostenlos, der Rest braucht Pro.

    • Tailwind CSS
    • Design Tokens
    • Dark Mode
    • OKLCH
    • Design-Systeme
    • Barrierefreiheit

    Teilen

    SR

    Über den Autor

    Serban Rusu

    Gründer von Wingo UI

    Serban Rusu hat Wingo UI gegründet. Er baut die Komponenten, die CLI und den MCP-Server und schreibt über React-UIs für Smartphones und KI-Agents.

    Mehr von Serban
    Weiterlesen

    Ähnliche Artikel

    Alle Artikel
    Next.js und Tailwind CSS

    shadcn Vite Setup mit Tailwind CSS v4 und React 19

    shadcn Vite Setup, getestet mit Vite 8 und TypeScript 6: Tailwind-v4-Plugin, @-Alias in tsconfig.app.json, Dark Mode ohne Next.js, dann Komponenten hinzufügen.

    SRSerban Rusu·13. Okt. 2026·7 Min. Lesezeit
    UI mit KI-Agenten bauen

    Claude Code Design System: 50 Screens, ein Look

    Claude Code Design System einrichten: Tokens, Größentabellen und Komponentenregeln für den Agenten, dazu Checks, die zeigen, ob 50 Screens zusammenpassen.

    SRSerban Rusu·16. Okt. 2026·12 Min. Lesezeit
    Next.js und Tailwind CSS

    Next.js Component Library im App Router einrichten

    Next.js Component Library im App Router einrichten, getestet mit Next.js 16.4: use client, Tokens in globals.css, Dark Mode ohne Flackern, Updates per CLI.

    SRSerban Rusu·13. Okt. 2026·8 Min. Lesezeit
    Newsletter

    Neue Artikel per E-Mail

    Neue Guides, Tutorials und Vergleiche aus dem Wingo UI Blog, sobald sie erscheinen. Die meisten Artikel sind auf Englisch.

    Kein Spam. Du kannst dich jederzeit abmelden.

    WingoUI

    Animierte, konfigurierbare, mobile-first React-Komponenten und Blöcke. Kopier den Quellcode, pass ihn an und lass deinen Coding-Agenten damit bauen.

    Produkt

    • Komponenten
    • Vorlagen
    • Preise
    • KI-Agenten
    • Theme-Generator

    Ressourcen

    • Doku
    • Blog
    • Changelog
    • Changelog-RSS
    • llms.txt

    Rechtliches

    • AGB
    • Datenschutz
    • Rückerstattung
    • Lizenz
    • Impressum

    Komponenten-Kategorien

    • Buttons & Actions
    • Inputs
    • Forms
    • Navigation
    • Overlays
    • Feedback
    • Data Display
    • Tables & Lists
    • Charts & Stats
    • Layout
    • Media
    • AI Kit
    • Text & Effects
    • Mobile
    • Commerce
    • Marketing Sections
    • Blocks
    • Hooks & Utilities

    © 2026 Wingo UI

    Wingo UI