eine eigene Komponenten-Bibliothek aufbauen
Ein React Native Design System bündelt Design Tokens, geprüfte Komponenten und Theming in einem versionierten Paket, damit Teams nicht bei jeder neuen App wieder Buttons, Karten und Formulare von Grund auf neu bauen. Dieser Artikel zeigt, wie eine eigene Komponenten-Bibliothek von den ersten Tokens bis zum wiederverwendbaren npm-Paket entsteht.
Inhaltsverzeichnis
- 1. Warum ein eigenes Design System in React Native?
- 2. Design Tokens: die Single Source of Truth
- 3. Primitive Komponenten: Button, Text, Card, Input
- 4. Theming mit Context und ThemeProvider
- 5. Styling-Strategie: NativeWind versus StyleSheet
- 6. Storybook für React Native: isolierte Entwicklung
- 7. Monorepo und Paketierung als npm-Paket
- 8. Versionierung, Changelog und Breaking Changes
- 9. Design System im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum ein eigenes Design System in React Native?
Ein Design System ist mehr als eine Sammlung hübscher Komponenten. Es ist die vertraglich verbindliche Schnittstelle zwischen Produktdesign und Entwicklung: Farben, Abstände, Typografie und Interaktionsmuster werden einmal definiert und danach konsequent wiederverwendet. Ohne dieses Fundament driften Apps innerhalb weniger Sprints auseinander, weil jedes Team seine eigenen Button-Varianten, Abstände und Schriftgrößen erfindet. Eine zentrale Komponenten-Bibliothek stoppt diese Fragmentierung, bevor sie entsteht.
Der Aufwand für ein eigenes React Native Design System lohnt sich vor allem, sobald mehr als eine App oder mehr als ein Team dieselben UI-Bausteine benötigt. Ein Unternehmen mit Kunden-App, internem Admin-Tool und einem Partner-Portal profitiert massiv davon, Button, Input und Card nur einmal zu pflegen. Bugfixes, Barrierefreiheits-Verbesserungen und neue Farbthemen landen dann zentral in der Bibliothek und propagieren sich über einen simplen Versions-Bump in alle Consumer-Apps.
Wichtig ist die Abgrenzung zu einer reinen Komponenten-Sammlung: Ein echtes Design System dokumentiert auch Entscheidungen, etwa warum ein bestimmter Abstand als Standard gilt oder wann welche Button-Variante verwendet wird. Diese Dokumentation lebt idealerweise direkt neben dem Code, zum Beispiel in Storybook, damit Design und Entwicklung dieselbe Quelle der Wahrheit nutzen und Rückfragen im Chat auf ein Minimum sinken.
2. Design Tokens: die Single Source of Truth
Design Tokens sind die atomaren Werte eines Design Systems: Farben, Spacing-Stufen, Schriftgrößen, Radien und Schattentiefen als benannte Konstanten statt als verstreute Hex-Codes im Code. Der entscheidende Vorteil einer Komponenten-Bibliothek mit Tokens liegt darin, dass eine Farbänderung an genau einer Stelle passiert und sich automatisch durch die gesamte App zieht, statt in fünfzig Dateien manuell gesucht und ersetzt zu werden.
In der Praxis definiert man Tokens entweder als reines TypeScript-Objekt oder als plattformneutrales tokens.json, aus dem ein Build-Schritt sowohl die React Native Konstanten als auch, falls vorhanden, Design-Tool-Exporte (Figma Tokens Plugin) generiert. Diese Entkopplung sorgt dafür, dass Designer und Entwickler dieselben Werte referenzieren, ohne dass jemand von Hand zwischen Figma und Code synchronisieren muss.
// tokens.ts — single source of truth for the design system
export const colors = {
primary: '#4338ca',
primaryDark: '#312e81',
surface: '#ffffff',
surfaceMuted: '#f1f5f9',
textPrimary: '#0f172a',
textMuted: '#64748b',
danger: '#dc2626',
} as const;
export const spacing = {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
} as const;
export const radii = {
sm: 6,
md: 12,
lg: 20,
full: 999,
} as const;
export const typography = {
bodySize: 16,
headingSize: 22,
lineHeightBody: 22,
fontFamilyBase: 'Inter-Regular',
fontFamilyBold: 'Inter-Bold',
} as const;
3. Primitive Komponenten: Button, Text, Card, Input
Auf den Tokens baut die erste Schicht des Design Systems auf: die primitiven Komponenten. Button, Text, Card und Input sind die Bausteine, aus denen später jede Bildschirmansicht zusammengesetzt wird. Jede primitive Komponente in der Komponenten-Bibliothek sollte über Varianten-Props gesteuert werden, etwa variant="primary" | "secondary" | "danger" beim Button, statt beliebige Style-Overrides von außen zuzulassen. Das verhindert, dass jede Consumer-App den Button optisch leicht anders interpretiert.
Ein zweiter wichtiger Grundsatz: Primitive Komponenten kapseln Verhalten, nicht nur Aussehen. Ein Button sollte einen Ladezustand, einen Disabled-Zustand und korrekte accessibilityRole- sowie accessibilityState-Props von Haus aus mitbringen, damit niemand diese Logik in jeder App neu erfindet und dabei Barrierefreiheits-Details vergisst. Genau hier zeigt sich der Wert eines echten Design Systems gegenüber einer Ansammlung loser UI-Snippets.
Komposition schlägt Vererbung: Statt eine riesige Card-Komponente mit zwanzig optionalen Props zu bauen, kombiniert man kleinere Bausteine wie CardHeader, CardBody und CardFooter. Das hält jede einzelne Komponente überschaubar, testbar und leicht dokumentierbar, und neue Layout-Varianten entstehen durch Neuanordnung statt durch neue Props.
// Button.tsx — primitive component with variant-driven styling
import { Pressable, Text, ActivityIndicator, StyleSheet } from 'react-native';
import { colors, spacing, radii } from '../tokens';
type ButtonVariant = 'primary' | 'secondary' | 'danger';
interface ButtonProps {
label: string;
onPress: () => void;
variant?: ButtonVariant;
loading?: boolean;
disabled?: boolean;
}
export function Button({ label, onPress, variant = 'primary', loading, disabled }: ButtonProps) {
const isDisabled = disabled || loading;
return (
<Pressable
onPress={onPress}
disabled={isDisabled}
accessibilityRole="button"
accessibilityState={{ disabled: isDisabled, busy: loading }}
style={({ pressed }) => [
styles.base,
styles[variant],
isDisabled && styles.disabled,
pressed && styles.pressed,
]}
>
{loading ? <ActivityIndicator color="#fff" /> : <Text style={styles.label}>{label}</Text>}
</Pressable>
);
}
const styles = StyleSheet.create({
base: { paddingVertical: spacing.sm, paddingHorizontal: spacing.lg, borderRadius: radii.md },
primary: { backgroundColor: colors.primary },
secondary: { backgroundColor: colors.surfaceMuted },
danger: { backgroundColor: colors.danger },
disabled: { opacity: 0.5 },
pressed: { opacity: 0.85 },
label: { color: '#fff', fontWeight: '600', textAlign: 'center' },
});
4. Theming mit Context und ThemeProvider
Sobald mehrere Marken, ein Dark Mode oder White-Label-Kunden ins Spiel kommen, reichen statische Tokens nicht mehr aus. Hier kommt ein ThemeProvider auf Basis von React Context zum Einsatz, der die aktiven Tokens zur Laufzeit austauscht, ohne dass jede Komponente ihre Importe ändern muss. Primitive Komponenten lesen ihre Farben und Abstände dann über einen useTheme()-Hook statt über direkte Token-Importe, was das gesamte React Native Design System theming-fähig macht, ohne den Komponenten-Code selbst anzufassen.
Ein häufiger Fehler ist, Theming nachträglich in eine bereits gewachsene Bibliothek einzuziehen. Deutlich robuster ist es, den ThemeProvider von Anfang an als verpflichtende Wurzel der Komponenten-Bibliothek zu definieren, selbst wenn zunächst nur ein einziges Theme existiert. So bleibt der spätere Wechsel zu Dark Mode oder Multi-Brand-Support eine additive Änderung statt eines invasiven Refactorings quer durch alle Komponenten.
// ThemeProvider.tsx — runtime-swappable design tokens via Context
import { createContext, useContext, useState, type ReactNode } from 'react';
import { colors as lightColors } from '../tokens';
const darkColors = {
...lightColors,
surface: '#0f172a',
textPrimary: '#f1f5f9',
};
type Theme = { colors: typeof lightColors; mode: 'light' | 'dark' };
const ThemeContext = createContext<{ theme: Theme; toggleMode: () => void } | null>(null);
export function ThemeProvider({ children }: { children: ReactNode }) {
const [mode, setMode] = useState<'light' | 'dark'>('light');
const theme: Theme = { colors: mode === 'dark' ? darkColors : lightColors, mode };
return (
<ThemeContext.Provider value={{ theme, toggleMode: () => setMode(m => m === 'light' ? 'dark' : 'light') }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within a ThemeProvider');
return ctx;
}
5. Styling-Strategie: NativeWind versus StyleSheet
Bei der Styling-Strategie stehen Teams meist vor der Wahl zwischen klassischem StyleSheet.create und einem Utility-First-Ansatz wie NativeWind, das Tailwind-Klassen auf React Native überträgt. Für ein Design System spricht viel für NativeWind: Die Tokens lassen sich direkt in tailwind.config.js als Farben, Spacing-Skala und Radien hinterlegen, wodurch Klassennamen wie bg-primary oder p-md automatisch der zentralen Token-Definition entsprechen, statt Magic Numbers im Code zu verstreuen.
Klassisches StyleSheet bleibt trotzdem relevant, insbesondere für komplexe, dynamische Styles, die von Animationen oder Layout-Messungen abhängen, wo Utility-Klassen an ihre Grenzen stoßen. Viele produktive Komponenten-Bibliotheken kombinieren daher beide Ansätze: NativeWind für den überwiegenden Teil des statischen Layouts, StyleSheet für performancekritische oder dynamisch berechnete Styles. Wichtig ist nur, dass beide Wege dieselben Tokens referenzieren, damit visuelle Konsistenz nicht vom gewählten Styling-Mechanismus abhängt.
6. Storybook für React Native: isolierte Entwicklung
Storybook erlaubt es, jede Komponente der Bibliothek isoliert von einer echten App zu entwickeln und zu testen. Statt eine Testansicht mitten in der Haupt-App zu bauen, um einen neuen Button zu prüfen, startet man eine Story, die genau diesen Button mit allen relevanten Props-Kombinationen zeigt. Das beschleunigt die Entwicklung spürbar und macht neue Varianten für das gesamte Team, inklusive Design, sofort sichtbar und diskutierbar.
Für React Native gibt es zwei praktikable Wege: Storybook direkt im Simulator über @storybook/react-native, oder Storybook im Browser über React Native Web, was besonders für schnelle Reviews im Pull-Request-Prozess praktisch ist, da kein Emulator gestartet werden muss. Viele Teams nutzen beides parallel, weil die Web-Variante für CI-Snapshot-Vergleiche einfacher zu automatisieren ist, während die native Variante reale Touch-Interaktionen und Plattform-Eigenheiten testet.
# Initialize Storybook for a React Native design system package
npx storybook@latest init --type react_native
# Run Storybook in the iOS simulator
yarn ios --scheme UILibraryStorybook
# Run the web variant for fast PR reviews without a simulator
yarn storybook:web
7. Monorepo und Paketierung als npm-Paket
Damit mehrere Apps dieselbe Komponenten-Bibliothek konsumieren können, muss sie als eigenständiges Paket existieren, nicht als kopierter Ordner. Ein Yarn- oder npm-Workspaces-Monorepo ist dafür der pragmatischste Einstieg: Die Bibliothek liegt unter packages/ui, die Consumer-Apps unter apps/customer und apps/admin, und alle referenzieren die Bibliothek über einen Workspace-Verweis, ohne einen echten npm-Publish-Zyklus für jede lokale Änderung zu benötigen.
Sobald die Bibliothek auch außerhalb des eigenen Monorepos genutzt werden soll, etwa von einem Partner-Team oder einer separaten Codebasis, wird ein privates npm-Paket über eine private Registry wie GitHub Packages oder Verdaccio zur nächsten logischen Stufe. Wichtig ist in diesem Schritt eine saubere package.json mit korrekten peerDependencies für react und react-native, damit die Bibliothek nicht versehentlich eine eigene, inkompatible React-Version mitbringt.
{
"name": "@company/ui",
"version": "2.3.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"peerDependencies": {
"react": ">=18.2.0",
"react-native": ">=0.74.0"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com"
},
"files": ["dist"]
}
8. Versionierung, Changelog und Breaking Changes
Ein Design System ohne Versionsdisziplin wird schnell zur Quelle von Frustration: Ein Team ändert das Verhalten des Button, eine andere App bricht beim nächsten Update unbemerkt. Semantic Versioning ist hier Pflicht, nicht Kür. Patch-Versionen für Bugfixes, Minor-Versionen für neue, abwärtskompatible Props und Varianten, Major-Versionen für Breaking Changes wie eine geänderte Prop-Signatur oder ein entferntes Farbtoken.
Tools wie Changesets automatisieren diesen Prozess: Jede Pull-Request-Änderung an der Komponenten-Bibliothek bekommt eine kleine Changeset-Datei, aus der beim Release automatisch die Versionsnummer und ein lesbares Changelog generiert werden. Das entkoppelt die Entscheidung "welche Änderung ist wie gravierend" vom eigentlichen Release-Zeitpunkt und verhindert, dass ein Maintainer im Kopf mitverfolgen muss, was sich seit dem letzten Tag alles angesammelt hat.
9. Design System im direkten Vergleich
Ob sich der Aufbau eines eigenen React Native Design Systems lohnt, zeigt sich am deutlichsten im direkten Vergleich mit dem Status quo vieler Teams: Komponenten werden App für App neu geschrieben oder per Copy-Paste übernommen.
| Dimension | Ad-hoc Copy-Paste | Versioniertes Design System |
|---|---|---|
| Bugfix-Verteilung | Manuell in jeder App nachziehen | Ein Versions-Bump für alle Consumer |
| Visuelle Konsistenz | Driftet mit jeder App auseinander | Zentrale Tokens erzwingen Konsistenz |
| Barrierefreiheit | Wird pro App unterschiedlich umgesetzt | Einmal korrekt implementiert, überall geerbt |
| Onboarding neuer Entwickler | Muss jede App-eigene Lösung neu lernen | Storybook-Dokumentation als zentrale Referenz |
| Theming / Dark Mode | Aufwendiger Nachbau pro App | Additive Erweiterung über ThemeProvider |
Die Tabelle macht deutlich, dass der initiale Mehraufwand für Tokens, Theming und Paketierung sich erst amortisiert, sobald wirklich mehrere Apps oder Teams von der Komponenten-Bibliothek profitieren. Für ein einzelnes, kleines Projekt kann ein vollausgebautes Design System übertrieben sein, für jede Organisation mit zwei oder mehr React-Native-Apps ist es fast immer die günstigere Option auf mittlere Sicht.
Mironsoft
React Native Entwicklung, Design Systeme und App-Architektur
Ein eigenes Design System für eure React Native Apps?
Wir bauen und pflegen React Native Komponenten-Bibliotheken mit Design Tokens, Theming, Storybook-Dokumentation und sauberer Monorepo-Paketierung, damit eure Teams konsistente Apps schneller ausliefern.
Token-Architektur
Farben, Spacing, Typografie als versionierte Single Source of Truth
Komponenten-Aufbau
Primitive Komponenten mit Theming, Storybook und Barrierefreiheit
Monorepo-Setup
Workspaces, Paketierung und Versionierungs-Workflow mit Changesets
10. Zusammenfassung
Ein eigenes React Native Design System beginnt nicht bei Komponenten, sondern bei Design Tokens als Single Source of Truth für Farben, Spacing und Typografie. Darauf aufbauend entstehen primitive Komponenten mit klaren Varianten-Props, ein ThemeProvider für Theming zur Laufzeit, und eine bewusste Styling-Strategie zwischen NativeWind und StyleSheet. Storybook macht jede Komponente isoliert entwickel- und dokumentierbar, ein Monorepo mit Workspaces erlaubt gemeinsame Nutzung über mehrere Apps hinweg.
Der entscheidende Unterschied zu einer losen Komponenten-Sammlung ist Disziplin bei Versionierung und Changelog. Eine Komponenten-Bibliothek, die Semantic Versioning konsequent durchzieht, wird zur verlässlichen Grundlage für mehrere Apps, statt zur Quelle unerwarteter Breaking Changes. Der Aufwand lohnt sich, sobald mehr als ein Team oder mehr als eine App von derselben UI-Basis profitiert.
React Native Design System — Das Wichtigste auf einen Blick
Design Tokens
Farben, Spacing und Typografie als benannte Konstanten statt verstreuter Magic Numbers im Code.
Theming
Ein ThemeProvider auf Context-Basis macht Dark Mode und Multi-Brand-Support additiv statt invasiv.
Storybook
Isolierte Entwicklung und lebende Dokumentation für Design und Entwicklung gemeinsam.
Monorepo & Versionierung
Workspaces für gemeinsame Nutzung, Semantic Versioning und Changesets gegen unerwartete Breaking Changes.