React hat keine Meinung zu Styling. Es gibt kein eingebautes CSS-System, keine vorgeschriebene Methodik, keine “richtige” Lösung. Sie können globale Stylesheets verwenden wie 1995, Inline-Styles wie 2005, CSS Modules wie 2015, oder CSS-in-JS wie 2020. Alles funktioniert, alles ist gültig.
Diese Freiheit ist Segen und Fluch. Segen, weil Sie die Methode wählen können, die für Ihr Projekt passt. Fluch, weil es keine klare Antwort gibt und jedes Team anders vorgeht.
Dieses Kapitel erklärt die verschiedenen Styling-Ansätze in React – technisch, ehrlich, mit praktischen Empfehlungen. Was funktioniert wie? Was sind die Trade-offs? Was verwenden professionelle Teams wirklich? Und vor allem: Wie entscheidet man sich?
CSS wurde für Dokumente entworfen, nicht für Komponenten. Selektoren
sind global. .button gilt für jedes Element mit dieser
Klasse, überall im DOM. Das war früher kein Problem – Websites hatten
ein Stylesheet, eine styles.css, fertig.
Moderne Anwendungen haben Hunderte von Komponenten. Jede Komponente
könnte einen Button haben. Jeder Button soll anders aussehen. Aber alle
heißen .button. Konflikt.
/* Header.css */
.button {
background: blue;
color: white;
}
/* Sidebar.css */
.button {
background: gray;
border: 1px solid black;
}Welcher Style gewinnt? Der zuletzt geladene. Welcher ist das? Hängt von der Import-Reihenfolge ab. Nicht deterministisch, schwer zu debuggen.
Das ist das CSS-Scope-Problem. React löst es nicht automatisch. Aber React ermöglicht verschiedene Lösungen.
Der klassische Weg. Eine oder mehrere CSS-Dateien, die global gelten.
// main.tsx
import './index.css';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
/* index.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background: #f5f5f5;
}
.button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.button-primary {
background: blue;
color: white;
}
.button-secondary {
background: gray;
color: black;
}function Button({ variant = 'primary' }: { variant?: 'primary' | 'secondary' }) {
return <button className={`button button-${variant}`}>Click me</button>;
}
Vorteile: - Einfach, bekannt, kein Setup - Performant (ein Request, einmal geparst) - CSS ist pure CSS, keine JS-Overhead
Nachteile: - Globaler Scope – Konflikte möglich - Schwer zu warten bei großen Apps - Tote Styles (unused CSS) schwer zu finden - Keine Typisierung, keine IDE-Unterstützung
Wann verwenden: - Kleine Projekte (<10 Komponenten) - Prototypen, Demos - Globale Basis-Styles (Resets, Fonts, Variablen)
CSS-Dateien pro Komponente, aber immer noch global wirkend.
src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ └── Button.css
│ └── Header/
│ ├── Header.tsx
│ └── Header.css
// Button.tsx
import './Button.css';
function Button() {
return <button className="btn">Click me</button>;
}
/* Button.css */
.btn {
padding: 10px 20px;
background: blue;
color: white;
}Die CSS-Datei wird nur geladen, wenn die Komponente importiert wird.
Aber die Styles sind trotzdem global. .btn gilt im gesamten
DOM.
Vorteile: - Organisation: CSS neben Komponente - Lazy Loading: Nur geladene Komponenten haben Styles - Einfach zu verstehen
Nachteile: - Immer noch global – Klassenname-Konflikte möglich - Naming Conventions nötig (BEM, SMACSS) - Keine echte Kapselung
Wann verwenden: - Mittlere Projekte mit klarer Komponenten-Struktur - Teams, die CSS-Methodiken wie BEM nutzen - Wenn globales CSS OK ist, aber Organisation wichtig
CSS Modules lösen das Scope-Problem. Klassennamen werden zur Build-Zeit in eindeutige Hashes transformiert.
// Button.tsx
import styles from './Button.module.css';
function Button() {
return <button className={styles.btn}>Click me</button>;
}
/* Button.module.css */
.btn {
padding: 10px 20px;
background: blue;
color: white;
}
.btn:hover {
background: darkblue;
}Der Compiler transformiert .btn zu
.Button_btn__a3f2d (oder ähnlich). Jede Komponente bekommt
eindeutige Klassennamen.
<!-- Gerendert -->
<button class="Button_btn__a3f2d">Click me</button>TypeScript-Integration:
// TypeScript inferiert automatisch
const styles: {
btn: string;
btnPrimary: string;
// ...
}
Autocomplete funktioniert:
<button className={styles.btn}> // ✓ Autocomplete
<button className={styles.buton}> // ❌ TypeScript-Fehler
Composition:
/* Button.module.css */
.base {
padding: 10px 20px;
border: none;
border-radius: 4px;
}
.primary {
composes: base;
background: blue;
color: white;
}
.secondary {
composes: base;
background: gray;
color: black;
}<button className={styles.primary}>Primary</button>
<button className={styles.secondary}>Secondary</button>
Globale Styles in Modules:
/* Button.module.css */
.btn {
/* Lokal, gescoped */
}
:global(.override) {
/* Global, ungescoped */
color: red;
}Vorteile: - Echte Kapselung – keine Konflikte - TypeScript-Support - CSS bleibt CSS (kein JS) - Performant - Tree-shaking möglich (unused styles werden entfernt)
Nachteile: - Syntax gewöhnungsbedürftig
(.module.css) - Klassennamen in JS-Objekten, nicht in
Strings - Debugging: HTML zeigt Hash-Namen, nicht semantische
Wann verwenden: - Mittlere bis große Projekte - Teams, die CSS bevorzugen aber Kapselung wollen - TypeScript-Projekte (Autocomplete!) - Wenn CSS-in-JS zu viel Overhead ist
Styles direkt im JSX, als JavaScript-Objekt.
function Button() {
const buttonStyle = {
padding: '10px 20px',
background: 'blue',
color: 'white',
border: 'none',
borderRadius: '4px'
};
return <button style={buttonStyle}>Click me</button>;
}
Oder inline:
<button style={{ background: 'blue', color: 'white' }}>Click me</button>
Dynamische Styles:
function Button({ isPrimary }: { isPrimary: boolean }) {
return (
<button
style={{
background: isPrimary ? 'blue' : 'gray',
color: 'white'
}}
>
Click me
</button>
);
}
Vorteile: - Kein separates File - Voll dynamisch (Props, State) - Kein Build-Schritt - JavaScript-Power (Berechnungen, Ternäre Operatoren)
Nachteile: - Keine Pseudoklassen
(:hover, :focus) - Keine Media Queries - Kein
CSS-Tooling (Autoprefixer, etc.) - Performance: Neues Objekt bei jedem
Render (außer mit useMemo) - Schwer zu warten bei vielen Styles
Vorsicht vor Performance-Fallen:
// ❌ Neues Objekt bei jedem Render
function Button() {
return <button style={{ background: 'blue' }}>Click</button>;
}
// ✓ Konstante außerhalb
const buttonStyle = { background: 'blue' };
function Button() {
return <button style={buttonStyle}>Click</button>;
}
// ✓ Oder useMemo für dynamische Styles
function Button({ color }: { color: string }) {
const style = useMemo(() => ({ background: color }), [color]);
return <button style={style}>Click</button>;
}
Wann verwenden: - Kleine, dynamische Style-Änderungen - Prototyping - Wenn nur wenige Properties sich ändern - NICHT als Haupt-Styling-Methode
CSS wird in JavaScript geschrieben, oft als Tagged Template Literals.
styled-components:
import styled from 'styled-components';
const Button = styled.button<{ $primary?: boolean }>`
padding: 10px 20px;
border: none;
border-radius: 4px;
background: ${props => props.$primary ? 'blue' : 'gray'};
color: white;
cursor: pointer;
&:hover {
opacity: 0.8;
}
@media (max-width: 768px) {
padding: 8px 16px;
}
`;
// Verwendung
<Button $primary>Click me</Button>
<Button>Secondary</Button>
Das $-Präfix verhindert, dass Props als DOM-Attribute
durchgereicht werden.
Emotion:
import { css } from '@emotion/react';
function Button({ primary }: { primary?: boolean }) {
return (
<button
css={css`
padding: 10px 20px;
background: ${primary ? 'blue' : 'gray'};
color: white;
&:hover {
opacity: 0.8;
}
`}
>
Click me
</button>
);
}
Theming:
import { ThemeProvider } from 'styled-components';
const theme = {
colors: {
primary: 'blue',
secondary: 'gray'
},
spacing: {
small: '8px',
medium: '16px'
}
};
const Button = styled.button`
padding: ${props => props.theme.spacing.medium};
background: ${props => props.theme.colors.primary};
`;
function App() {
return (
<ThemeProvider theme={theme}>
<Button>Themed Button</Button>
</ThemeProvider>
);
}
Vorteile: - Volle JavaScript-Power - Props-basierte Styles - Theming out-of-the-box - Pseudoklassen, Media Queries funktionieren - Automatisches Vendor-Prefixing - TypeScript-Support - Komponenten-Scoping automatisch
Nachteile: - Runtime-Overhead (Styles werden zur Laufzeit generiert) - Größere Bundle-Size - Lernkurve - Debugging komplexer (dynamisch generierte Klassennamen) - SSR kompliziert
Wann verwenden: - Design-Systeme - Apps mit komplexen Theming-Anforderungen - Wenn volle JavaScript-Integration wichtig ist - Große Teams mit etabliertem CSS-in-JS-Setup
Statt eigene Klassen zu schreiben, kombiniert man vordefinierte Utility-Klassen.
function Button({ variant = 'primary' }: { variant?: 'primary' | 'secondary' }) {
const baseClasses = 'px-4 py-2 rounded font-semibold';
const variantClasses = variant === 'primary'
? 'bg-blue-500 text-white hover:bg-blue-600'
: 'bg-gray-300 text-black hover:bg-gray-400';
return (
<button className={`${baseClasses} ${variantClasses}`}>
Click me
</button>
);
}
Oder mit clsx für bedingte Klassen:
import clsx from 'clsx';
function Button({ variant = 'primary', disabled }: ButtonProps) {
return (
<button
className={clsx(
'px-4 py-2 rounded font-semibold',
{
'bg-blue-500 text-white hover:bg-blue-600': variant === 'primary',
'bg-gray-300 text-black hover:bg-gray-400': variant === 'secondary',
'opacity-50 cursor-not-allowed': disabled
}
)}
>
Click me
</button>
);
}
Vorteile: - Extrem schnelle Entwicklung - Konsistentes Design durch vordefinierte Werte - Kein Context-Switching (CSS und HTML in einem) - Purge-Funktion entfernt unused CSS (kleine Bundles) - Responsive und Dark-Mode out-of-the-box
Nachteile: - HTML wird sehr verbose - Lernkurve (Utility-Klassen lernen) - Wiederverwendung durch Komponenten, nicht durch CSS - Semantic Classnames gehen verloren
Wann verwenden: - Moderne Projekte mit schneller Entwicklung - Teams, die Utility-First-Ansatz bevorzugen - Wenn Design-Konsistenz wichtiger ist als Custom-Styles - Prototyping
Die React-Community ist gespalten. Verschiedene Projekte, verschiedene Präferenzen.
Kleine Startups, MVPs: - Tailwind CSS (schnelle Entwicklung, konsistentes Design) - Oder CSS Modules (wenn Team CSS bevorzugt)
Große Enterprises: - CSS-in-JS (styled-components, Emotion) für Design-Systeme - Oder CSS Modules mit strengen Naming Conventions - Tailwind gewinnt auch hier an Boden
Open-Source UI-Libraries: - styled-components oder Emotion (Theme-Support wichtig) - Zunehmend auch Tailwind + HeadlessUI
Developer-Tools, Dashboards: - Tailwind (Geschwindigkeit) - CSS Modules (wenn kein Theming nötig)
Trends 2025: - Tailwind CSS steigt stark - CSS-in-JS verliert an Popularität (Runtime-Overhead) - CSS Modules bleibt solide Wahl - Neue Ansätze: Vanilla Extract, Panda CSS (Zero-Runtime CSS-in-JS)
| Methode | Beliebtheit | Trend | Typischer Use-Case |
|---|---|---|---|
| Globales CSS | 📉 Sinkend | Legacy | Kleine Projekte, Learning |
| CSS Modules | 📊 Stabil | Gleichbleibend | Mittlere bis große Apps |
| CSS-in-JS | 📉 Sinkend | Rückläufig | Design-Systeme (Legacy) |
| Tailwind | 📈 Steigend | Stark wachsend | Moderne Projekte aller Größen |
| Inline Styles | 🔹 Nische | Stabil | Dynamische Einzelwerte |
Wählen Sie globales CSS, wenn: - Ihr Projekt klein ist (<10 Komponenten) - Sie prototypen - Sie CSS-Grundlagen lernen
Wählen Sie CSS Modules, wenn: - Sie mittlere bis große Apps bauen - Ihr Team CSS bevorzugt - Sie Typisierung wollen - Sie keine Runtime-Overhead wollen
Wählen Sie CSS-in-JS, wenn: - Sie ein Design-System bauen - Theming zentral ist - Sie bereits CSS-in-JS nutzen (Migration teuer) - Runtime-Overhead akzeptabel ist
Wählen Sie Tailwind, wenn: - Sie schnell entwickeln wollen - Design-Konsistenz wichtig ist - Sie moderne Tools bevorzugen - Ihr Team Utility-First mag
Verwenden Sie Inline Styles für: - Einzelne dynamische Properties - Quick Prototyping - NICHT als Haupt-Styling-Methode
CSS Modules:
// Button.module.css
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
font-weight: 600;
cursor: pointer;
}
.primary {
composes: btn;
background: #3b82f6;
color: white;
}
.primary:hover {
background: #2563eb;
}
.secondary {
composes: btn;
background: #e5e7eb;
color: #1f2937;
}
.secondary:hover {
background: #d1d5db;
}
// Button.tsx
import styles from './Button.module.css';
interface ButtonProps {
variant?: 'primary' | 'secondary';
children: React.ReactNode;
onClick?: () => void;
}
export function Button({ variant = 'primary', children, onClick }: ButtonProps) {
return (
<button className={styles[variant]} onClick={onClick}>
{children}
</button>
);
}
Tailwind:
import clsx from 'clsx';
interface ButtonProps {
variant?: 'primary' | 'secondary';
children: React.ReactNode;
onClick?: () => void;
}
export function Button({ variant = 'primary', children, onClick }: ButtonProps) {
return (
<button
className={clsx(
'px-4 py-2 rounded font-semibold cursor-pointer border-none',
{
'bg-blue-500 text-white hover:bg-blue-600': variant === 'primary',
'bg-gray-200 text-gray-800 hover:bg-gray-300': variant === 'secondary'
}
)}
onClick={onClick}
>
{children}
</button>
);
}
styled-components:
import styled from 'styled-components';
const StyledButton = styled.button<{ $variant: 'primary' | 'secondary' }>`
padding: 10px 20px;
border: none;
border-radius: 4px;
font-weight: 600;
cursor: pointer;
background: ${props => props.$variant === 'primary' ? '#3b82f6' : '#e5e7eb'};
color: ${props => props.$variant === 'primary' ? 'white' : '#1f2937'};
&:hover {
background: ${props => props.$variant === 'primary' ? '#2563eb' : '#d1d5db'};
}
`;
interface ButtonProps {
variant?: 'primary' | 'secondary';
children: React.ReactNode;
onClick?: () => void;
}
export function Button({ variant = 'primary', children, onClick }: ButtonProps) {
return (
<StyledButton $variant={variant} onClick={onClick}>
{children}
</StyledButton>
);
}
Alle drei funktionieren. CSS Modules: Typisiert, performant, CSS bleibt CSS. Tailwind: Schnell, konsistent, keine separaten Files. styled-components: Volle JS-Power, Theming eingebaut.
Für neue Projekte in 2025: Tailwind CSS oder CSS Modules.
Tailwind, wenn Sie schnell sein wollen, Design-Konsistenz schätzen, und moderne Tooling mögen. CSS Modules, wenn Sie klassisches CSS bevorzugen, volle IDE-Unterstützung wollen, und Runtime-Overhead vermeiden möchten.
CSS-in-JS nur, wenn Sie ein bestehendes Design-System haben oder sehr komplexes Theming brauchen. Die Runtime-Kosten sind real, und Zero-Runtime-Alternativen wie Vanilla Extract sind noch nicht ausgereift genug.
Globales CSS für globale Basics (Resets, Fonts). Inline Styles für kleine, dynamische Tweaks. Aber nie als Hauptmethode.
React gibt Ihnen die Freiheit. Nutzen Sie sie weise. Wählen Sie eine Methode, committen Sie sich dazu, und bleiben Sie konsistent. Das Schlimmste ist ein Mix aus allen Ansätzen im selben Projekt. Entscheiden Sie sich – und bauen Sie darauf auf.