8 Styling in React – Von global bis komponentenspezifisch

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?

8.1 Das Grundproblem: CSS ist global

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.

8.2 Ansatz 1: Globale Stylesheets

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)

8.3 Ansatz 2: Komponenten-spezifisches CSS

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

8.4 Ansatz 3: CSS Modules

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

8.5 Ansatz 4: Inline Styles

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

8.6 Ansatz 5: CSS-in-JS (styled-components, Emotion)

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

8.7 Ansatz 6: Utility-First CSS (Tailwind)

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

8.8 Was wird in der Praxis verwendet?

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

8.9 Entscheidungshilfe

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

8.10 Ein vollständiges Beispiel: Button-Komponente in verschiedenen Stilen

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.

8.11 Die ehrliche Empfehlung

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.