Das Kit wird mit 9 vorgefertigten Farbthemen geliefert, die das gesamte Erscheinungsbild deiner Anwendung über eine einzige
COLOR_THEME-Umgebungsvariable verändern. Jedes Theme bietet sowohl einen Light- als auch einen Dark-Mode-Variant, powered by next-themes für die Erkennung der Systemeinstellung und ein flimmerfreies Theme-Wechseln.Diese Seite behandelt die Theme-Auswahl, den Dark Mode, CSS Custom Properties und das Erstellen eigener Themes. Für das Styling von Komponenten, siehe UI-Komponenten. Für die Tailwind-Konfiguration, siehe Anpassung.
So funktioniert es
Das Theme-System fließt von einer Umgebungsvariable über CSS Custom Properties zu Tailwind-Utility-Klassen:
COLOR_THEME Umgebungsvariable
|
v
getActiveTheme() — validiert die Umgebungsvariable, fällt auf 'default' zurück
|
v
data-theme="ocean" auf <html> — wird im Root-Layout zur Build-Zeit gesetzt
|
v
CSS-Selektoren [data-theme='ocean'] — aktivieren das passende Theme-CSS
|
v
CSS Custom Properties (--primary, --background, ...)
|
v
Tailwind-Klassen (bg-primary, text-foreground, ...)
|
v
Dark-Mode-Toggle — fügt .dark-Klasse hinzu → [data-theme='ocean'].dark-Selektoren
Das Farbthema (welche Palette) und der Dark Mode (helle vs. dunkle Variante) sind unabhängige Systeme. Der Wechsel von
ocean zu forest ändert die Farbpalette. Das Umschalten des Dark Modes wechselt zwischen der hellen und der dunklen Variante des jeweils aktiven Themes.Das Theme-System ist reines CSS — es wird kein JavaScript ausgeführt, um Farbpaletten zu wechseln. Das
data-theme-Attribut wird zur Build-Zeit im Root-Layout gesetzt, und CSS-Selektoren übernehmen den Rest. Das bedeutet keinen Laufzeit-Overhead, kein Layout-Shift und keinen Flash of Unstyled Content. Alle 9 Themes sind mit WCAG-konformen Kontrastverhältnissen zwischen Vorder- und Hintergrundfarben in beiden Varianten (hell und dunkel) gestaltet.Verfügbare Themes
Die Theme-Registry definiert alle 9 verfügbaren Themes:
src/styles/themes/themes.ts — Verfügbare Themes
export const AVAILABLE_THEMES = [
'default',
'ocean',
'forest',
'sunset',
'midnight',
'coral',
'slate',
'aurora',
'crimson',
] as const
Jedes Theme ist für einen bestimmten Anwendungsfall konzipiert:
| Theme | Farbe | Geeignet für |
|---|---|---|
default | Blau | Professionelles SaaS, allgemeine Anwendungen |
ocean | Türkis | Maritime Projekte, Tech-Startups, Entwicklerwerkzeuge |
forest | Grün | Umweltfreundliche Produkte, Nachhaltigkeit, Gesundheit & Wellness |
sunset | Orange | Kreativwerkzeuge, Design-Plattformen, Marktplätze |
midnight | Lila | Premium-Enterprise, Fintech, Luxusprodukte |
coral | Pink | Consumer-Apps, soziale Plattformen, Lifestyle-Marken |
slate | Grau | Business-Tools, B2B-Plattformen, Analytics-Dashboards |
aurora | Cyan | Tech-Plattformen, KI-Produkte, innovative Startups |
crimson | Rot | Performance-Tools, Analytics, Gaming-Plattformen |
Themes wechseln
Um das Theme deiner Anwendung zu ändern, setze die
COLOR_THEME-Umgebungsvariable in deiner apps/boilerplate/.env.local:bash
# apps/boilerplate/.env.local
COLOR_THEME=ocean
Die Funktion
getActiveTheme() liest diese Variable und validiert sie gegen die Liste der verfügbaren Themes. Fehlt der Wert oder ist er ungültig, wird auf default zurückgegriffen:src/styles/themes/themes.ts — getActiveTheme()
export function getActiveTheme(): ThemeName {
const envTheme =
process.env.COLOR_THEME || process.env.NEXT_PUBLIC_COLOR_THEME
if (envTheme && AVAILABLE_THEMES.includes(envTheme as ThemeName)) {
return envTheme as ThemeName
}
// Fallback to default theme
return 'default'
}
Das Root-Layout ruft diese Funktion auf und setzt das Ergebnis als
data-theme-Attribut am <html>-Element:src/app/layout.tsx — Theme-Anwendung
// Get active color theme from environment variable
const colorTheme = getActiveTheme()
Da
COLOR_THEME eine serverseitige Umgebungsvariable ist, musst du deinen Dev-Server nach einer Änderung neu starten. Führe pnpm dev:boilerplate erneut aus, um das neue Theme zu sehen.Dark Mode
Der Dark Mode wird unabhängig vom Farbthema verwaltet. Er nutzt next-themes, um eine
.dark-Klasse am <html>-Element umzuschalten, was die dunkle Variante des aktuellen Farbthemas aktiviert.ThemeProvider
Der
ThemeProvider umhüllt die Anwendung und verwaltet den Dark-Mode-Zustand. Er erkennt die Systemeinstellung, speichert die Benutzerwahl in localStorage und wendet die .dark-Klasse an:src/providers/theme-provider.tsx
'use client'
import * as React from 'react'
import { ThemeProvider as NextThemesProvider } from 'next-themes'
export function ThemeProvider({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem={true}
disableTransitionOnChange
{...props}
>
{children}
</NextThemesProvider>
)
}
Der Provider wird im Root-Layout innerhalb der Provider-Hierarchie konfiguriert:
src/app/layout.tsx — Provider-Hierarchie mit ThemeProvider
const content = (
<html lang="en" suppressHydrationWarning data-theme={colorTheme}>
<body className={`${inter.className} ${jetbrainsMono.variable}`}>
<MSWProvider>
<QueryProvider>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<DemoProvider>
<TooltipProvider>
{children}
<Toaster
richColors
position="top-center"
duration={8000}
toastOptions={{
classNames: {
icon: '!self-start !mt-0.5',
description: '!whitespace-pre-line',
},
}}
/>
<FilteredAnalytics />
</TooltipProvider>
</DemoProvider>
</ThemeProvider>
</QueryProvider>
</MSWProvider>
</body>
</html>
)
ThemeToggle
Die
ThemeToggle-Komponente stellt einen Sonne/Mond-Button bereit, mit dem Nutzer zwischen hellem und dunklem Modus wechseln können:packages/ui/src/theme-toggle.tsx — Dark-Mode-Toggle
'use client'
import * as React from 'react'
import { Moon, Sun } from 'lucide-react'
import { useTheme } from 'next-themes'
export function ThemeToggle() {
// HYDRATION-FIX: Use resolvedTheme instead of theme
// `theme` can be 'system' which doesn't tell us the actual theme
// `resolvedTheme` always returns 'light' or 'dark' after hydration
const { resolvedTheme, setTheme } = useTheme()
const [mounted, setMounted] = React.useState(false)
// useEffect only runs on the client, so now we can safely show the UI
React.useEffect(() => {
setMounted(true)
}, [])
const toggleTheme = () => {
setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')
}
if (!mounted) {
return (
<button
disabled
className="flex items-center justify-center rounded-md p-2 text-muted-foreground transition-colors"
aria-label="Toggle theme"
>
<Moon className="h-4 w-4" />
</button>
)
}
return (
<button
onClick={toggleTheme}
className="flex items-center justify-center rounded-md p-2 text-muted-foreground transition-colors hover:text-foreground"
aria-label="Toggle theme"
>
{resolvedTheme === 'dark' ? (
<Moon className="h-4 w-4" />
) : (
<Sun className="h-4 w-4" />
)}
</button>
)
}
Wichtige Implementierungsdetails:
resolvedThemewird stattthemeverwendet, weilthemeden Wert'system'zurückgeben kann, der keinen Aufschluss über den tatsächlich aufgelösten Modus gibtmounted-State verhindert Hydration-Mismatches — der Toggle rendert während des SSR einen deaktivierten Platzhalter und zeigt die interaktive Version erst nach der clientseitigen Hydration- Der Toggle ruft
setTheme()von next-themes auf, das die.dark-Klasse am<html>-Element hinzufügt oder entfernt
Wenn
defaultTheme auf "system" gesetzt und enableSystem auf true ist, passt sich die Anwendung automatisch an die Betriebssystem-Einstellung des Nutzers an (hell/dunkel). Nutzer können dies mit dem Toggle überschreiben, und ihre Wahl wird in localStorage gespeichert.CSS Custom Properties
Jedes Theme definiert seine Farben als CSS Custom Properties mit HSL-Werten ohne den
hsl()-Wrapper. Das ist die Grundlage, die das gesamte Farbsystem mit Tailwinds Opacity-Modifikatoren zum Laufen bringt.Hier sind die Light-Mode-Variablen des Default-Themes:
src/styles/themes/default.css — Light-Mode-Variablen
[data-theme='default'] {
/* Light Mode */
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 221 83% 53%;
--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%;
--warning: 38 92% 50%;
--warning-foreground: 0 0% 100%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221 83% 53%;
--radius: 0.5rem;
}
Und die Dark-Mode-Variante:
src/styles/themes/default.css — Dark-Mode-Variablen
[data-theme='default'].dark {
/* Dark Mode */
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--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%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--warning: 45 93% 47%;
--warning-foreground: 0 0% 0%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 217.2 91.2% 59.8%;
}
Variablen-Referenz
| Variable | Zweck | Verwendungsbeispiel |
|---|---|---|
--background | Seitenhintergrund | bg-background |
--foreground | Primäre Textfarbe | text-foreground |
--card | Karten-/Oberflächen-Hintergrund | bg-card |
--card-foreground | Text auf Karten | text-card-foreground |
--primary | Marken-/Akzentfarbe | bg-primary, text-primary |
--primary-foreground | Text auf primären Hintergründen | text-primary-foreground |
--secondary | Sekundäre Elemente | bg-secondary |
--muted | Dezente Hintergründe | bg-muted |
--muted-foreground | Gedämpfter Text | text-muted-foreground |
--accent | Hover-/Fokus-Hervorhebungen | bg-accent |
--destructive | Fehler-/Gefahrenzustände | bg-destructive |
--border | Rahmen und Trennlinien | border-border |
--input | Formularfeld-Rahmen | border-input |
--ring | Fokus-Ring-Farbe | ring-ring |
--radius | Basis-Rahmenradius | rounded-lg, rounded-md |
CSS-Variablen verwenden rohe HSL-Werte wie
221 83% 53% — nicht hsl(221, 83%, 53%). Das ist erforderlich, damit Tailwinds Opacity-Modifier-Syntax funktioniert. Wenn du bg-primary/50 schreibst, generiert Tailwind hsl(221 83% 53% / 0.5). Wenn du den Wert in hsl() einwickelst, werden Opacity-Modifier nicht mehr funktionieren.Ein eigenes Theme erstellen
1
CSS-Datei erstellen
Füge eine neue Datei in
apps/boilerplate/src/styles/themes/ mit den Farbvariablen deines Themes hinzu. Verwende das Default-Theme als Ausgangspunkt:css
/* apps/boilerplate/src/styles/themes/ruby.css */
[data-theme='ruby'] {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--primary: 346 77% 49%;
--primary-foreground: 0 0% 98%;
/* ... alle Variablen definieren */
}
[data-theme='ruby'].dark {
--background: 346 30% 6%;
--foreground: 0 0% 98%;
--primary: 346 77% 59%;
--primary-foreground: 0 0% 9%;
/* ... alle Dark-Mode-Variablen definieren */
}
2
Theme registrieren
Füge deinen Theme-Namen zum
AVAILABLE_THEMES-Array und zum THEME_METADATA-Objekt in apps/boilerplate/src/styles/themes/themes.ts hinzu:typescript
export const AVAILABLE_THEMES = [
'default',
'ocean',
// ... bestehende Themes
'ruby', // Neues Theme hinzufügen
] as const
3
In globals.css importieren
Füge den Import am Anfang von
apps/boilerplate/src/app/globals.css hinzu, neben den anderen Theme-Importen:css
@import '../styles/themes/ruby.css';
4
Umgebungsvariable setzen
Aktualisiere deine
apps/boilerplate/.env.local, um das neue Theme zu verwenden:bash
COLOR_THEME=ruby
Starte deinen Dev-Server neu und das neue Theme ist aktiv.
Wichtige Dateien
| Datei | Zweck |
|---|---|
apps/boilerplate/src/styles/themes/themes.ts | Theme-Registry — verfügbare Themes, Metadaten, getActiveTheme() |
apps/boilerplate/src/styles/themes/default.css | Standard-Blau-Theme CSS-Variablen (hell + dunkel) |
apps/boilerplate/src/styles/themes/*.css | Alle 9 Theme-CSS-Dateien (ocean, forest, sunset, etc.) |
apps/boilerplate/src/app/layout.tsx | Root-Layout — setzt das data-theme-Attribut am <html> |
apps/boilerplate/src/providers/theme-provider.tsx | ThemeProvider-Wrapper für next-themes |
packages/ui/src/theme-toggle.tsx | Dark-Mode-Toggle-Button-Komponente |
apps/boilerplate/src/app/globals.css | Globale Stile — importiert alle Theme-CSS-Dateien |