Rich-Text-Editor-Styling mit Tailwind und TipTap: prose-Klasse richtig anpassen
AI generated
tw
Tailwind CSS · TipTap · Rich Text
Rich-Text-Editor-Styling mit TipTap
Die prose-Klasse, Toolbar-Zustände und den Unterschied zwischen Editor- und Render-Ansicht sauber lösen

TipTap baut auf ProseMirror auf und liefert bewusst keinerlei eigene visuelle Gestaltung mit, sondern nur die editierbare Struktur und die Befehle für Formatierung. Die gesamte Optik, vom Fließtext im Editor über die Toolbar-Buttons bis zur finalen Darstellung des gespeicherten Inhalts, entsteht ausschließlich durch eigenes CSS. Die @tailwindcss/typography-prose-Klasse liefert dafür eine solide Ausgangsbasis, muss aber gezielt angepasst werden, damit Editor-Ansicht und Render-Ansicht wirklich konsistent aussehen.

16 Min. Lesezeit prose · @tailwindcss/typography TipTap · ProseMirror

1. TipTap und ProseMirror: warum die Bibliothek keine eigenen Styles mitbringt

TipTap ist ein Toolkit rund um den ProseMirror-Editor-Kern und stellt eine deklarative, erweiterbare API für Formatierungsbefehle, Extensions und Schema-Definitionen bereit, ohne dabei ein einziges visuelles Design vorzugeben. Diese bewusste Entscheidung erlaubt es, denselben Editor in vollständig unterschiedlichen Design-Systemen einzusetzen, von einem minimalistischen Kommentarfeld bis zu einem vollwertigen Dokumenteneditor, ohne dass TipTap selbst dafür angepasst werden müsste.

Der editierbare Bereich eines TipTap-Editors ist im Kern ein contenteditable-Element, das ProseMirror verwaltet, aber visuell komplett ungestylt bleibt, bis eigenes CSS angewendet wird. Ohne diese Anpassung sehen Ueberschriften, Listen und Zitate im Editor genauso aus wie unformatierter Fließtext, obwohl die zugrunde liegende Dokumentstruktur bereits korrekt ist, was die Notwendigkeit einer bewussten Styling-Strategie von Anfang an deutlich macht.

2. Die prose-Klasse für den editierbaren Bereich anpassen

Die prose-Klasse aus dem @tailwindcss/typography-Plugin liefert vordefinierte Styles für typografische Elemente wie Ueberschriften, Absätze, Listen und Zitate, ursprünglich für statischen, aus Markdown gerenderten Inhalt gedacht. Auf den TipTap-Editor angewendet, meist über die editorProps.attributes.class-Konfiguration, bekommt der editierbare Bereich sofort eine konsistente Grundtypografie, ohne dass jedes einzelne HTML-Element manuell gestylt werden müsste.

Der Editor-Kontext braucht aber zusätzliche Anpassungen gegenüber reinem statischem Prose-Content, etwa einen sichtbaren Cursor mit ausreichendem Kontrast, eine klar erkennbare Fokus-Markierung für den gesamten editierbaren Bereich und ausreichend Innenabstand, damit Text nicht direkt am Rand des Editor-Containers klebt. Diese Anpassungen werden meist über zusätzliche Utility-Klassen neben prose ergänzt, statt die Typography-Plugin-Konfiguration selbst zu verändern.


const editor = useEditor({
  extensions: [StarterKit, Placeholder.configure({
    placeholder: "Beginne zu schreiben ...",
  })],
  editorProps: {
    attributes: {
      class:
        "prose prose-slate max-w-none focus:outline-none " +
        "min-h-[240px] px-4 py-3 rounded-lg border border-slate-200",
    },
  },
});

3. Unterschied zwischen Editor-Ansicht und finaler Render-Ansicht

Der editierbare Bereich und die spätere, nur lesbare Darstellung desselben Inhalts teilen sich zwar dieselbe zugrunde liegende HTML-Struktur, brauchen aber unterschiedliche Interaktions-Styles. Im Editor sind Cursor-Sichtbarkeit, Fokus-Ringe und eventuell Platzhalter-Text relevant, während die Render-Ansicht diese Interaktionshinweise komplett weglassen sollte, weil dort keine Bearbeitung stattfindet und solche Hinweise nur verwirren würden.

In der Praxis bewährt es sich, dieselbe prose-Basisklasse für beide Kontexte zu verwenden, aber über eine zusätzliche Modifier-Klasse wie is-editor nur im Editor-Kontext editorspezifische Regeln zu ergänzen. So bleibt die grundlegende Typografie, Überschriftengrößen, Zeilenabstände, Listenformatierung, garantiert identisch zwischen beiden Ansichten, während nur die interaktionsbezogenen Details wie Fokus-Ring und Cursor-Farbe zwischen Editor und Render-Ansicht variieren.

4. Toolbar-Buttons mit aktivem und inaktivem Zustand stylen

TipTaps editor.isActive('bold')-Methode liefert einen booleschen Zustand, der genau dann wahr ist, wenn die aktuelle Cursor-Position oder Textauswahl bereits die entsprechende Formatierung trägt. Dieser Zustand ist die Grundlage für visuelles Toolbar-Feedback: Ein aktiver Fett-Button sollte sich klar von einem inaktiven unterscheiden, meist über einen dunkleren Hintergrund oder eine farbige Umrandung, damit Nutzer jederzeit erkennen, welche Formatierung an der aktuellen Cursor-Position bereits aktiv ist.

Wichtig ist, dass der Formatierungsbefehl selbst unabhängig vom visuellen Zustand ausgeführt wird, etwa über editor.chain().focus().toggleBold().run(), während die Klassenzuweisung des Buttons rein deklarativ auf isActive reagiert. Diese Trennung stellt sicher, dass der visuelle Zustand immer synchron mit dem tatsächlichen Editor-Zustand bleibt, auch wenn sich die Formatierung durch Tastenkürzel statt durch einen Toolbar-Klick ändert.


function ToolbarButton({ editor, format, label }) {
  const isActive = editor.isActive(format);
  return (
    <button
      type="button"
      onClick={() => editor.chain().focus().toggleBold().run()}
      className={`rounded px-2.5 py-1.5 text-sm font-medium transition-colors ${
        isActive
          ? "bg-slate-800 text-white"
          : "text-slate-600 hover:bg-slate-100"
      }`}
      aria-pressed={isActive}
    >
      {label}
    </button>
  );
}

5. Platzhalter-Text-Styling mit der Placeholder-Extension

TipTaps offizielle Placeholder-Extension fügt über CSS-Pseudo-Elemente, genauer ::before auf dem ersten leeren Absatz, einen Platzhalter-Text ein, sobald der Editor keinen Inhalt enthält. Anders als bei einem klassischen input-Element mit nativem placeholder-Attribut muss dieses Verhalten bei einem contenteditable-basierten Editor explizit über CSS nachgebaut werden, weil der Browser für beliebig strukturierten, editierbaren Inhalt keinen eingebauten Platzhalter-Mechanismus bereitstellt.

Für konsistentes Erscheinungsbild sollte der Platzhalter-Text dieselbe Schriftgröße und denselben Zeilenabstand wie der eigentliche Fließtext verwenden, sich aber über eine hellere Textfarbe klar als Platzhalter zu erkennen geben, ähnlich wie bei nativen Formularfeldern. Ein häufiger Stilfehler ist ein zu starker Kontrastunterschied zwischen Platzhalter und echtem Text, der beim späteren Eintippen zu einem irritierenden, sichtbaren Farbsprung führt.


/* Platzhalter-Styling für TipTaps Placeholder-Extension */
.tiptap p.is-editor-empty:first-child::before {
  content: attr(data-placeholder);
  color: theme(colors.slate.400);
  float: left;
  height: 0;
  pointer-events: none;
}

6. Eigene prose-Overrides für Editor-spezifische Elemente

Die Standard-Prose-Styles decken klassische Textelemente gut ab, TipTap-Editoren enthalten aber oft zusätzliche, editorspezifische Elemente wie eingebettete Erwähnungen, Code-Blöcke mit Syntax-Highlighting oder interaktive Task-Listen mit Checkboxen, für die die Standard-Prose-Klasse keine sinnvollen Default-Styles mitbringt. Diese Elemente brauchen eigene, zusätzliche CSS-Regeln, die gezielt mit den prose-*-Modifier-Utilities kombiniert werden, etwa prose-code:bg-slate-100 für Inline-Code oder prose-a:text-sky-600 für Links.

Bei Task-Listen mit Checkboxen ist besonders wichtig, dass die native Checkbox-Darstellung des Browsers nicht mit den Prose-Listenstilen kollidiert, was in der Praxis meist bedeutet, das Listenelement selbst aus dem normalen list-style-Fluss zu nehmen und die Checkbox stattdessen über ein eigenes flex-Layout neben dem Textinhalt zu platzieren. Diese Sonderbehandlung einzelner Elementtypen ist der Hauptgrund, warum reine Standard-Prose-Konfiguration bei komplexeren TipTap-Editoren selten vollständig ausreicht.

7. Praxisbeispiel: eine vollständige Editor-Komponente mit Toolbar und Statusleiste

Eine vollständige Editor-Komponente kombiniert die angepasste prose-Klasse für den Inhaltsbereich, eine Toolbar mit klar erkennbaren aktiven Zuständen für Formatierungsbefehle und meist eine schmale Statusleiste, die Wortanzahl oder Speicherstatus anzeigt. Die Toolbar sollte visuell klar vom eigentlichen Editierbereich abgegrenzt sein, üblicherweise über eine dezente Trennlinie oder einen leicht abweichenden Hintergrundton, damit Nutzer sofort zwischen Steuerungselementen und Inhalt unterscheiden können.

Für die responsive Darstellung lohnt es sich, die Toolbar bei schmalen Bildschirmen horizontal scrollbar zu machen, statt Buttons umbrechen zu lassen, weil umgebrochene Toolbar-Zeilen wertvollen vertikalen Platz für den eigentlichen Editierbereich kosten. Eine horizontal scrollbare Toolbar mit dezent gestylter Scrollbar, wie sie sich auch für andere UI-Bereiche eignet, hält die Editor-Komponente auch auf kleinen Bildschirmen kompakt und funktional.

8. Dark Mode: prose-invert und die Editor-Cursor-Farbe

Das Typography-Plugin liefert mit prose-invert eine vorgefertigte Dark-Mode-Variante, die Textfarben, Ueberschriften und Listenmarkierungen automatisch für dunkle Hintergründe anpasst, sobald sie zusammen mit einer dark:-Variante aktiviert wird. Diese Anpassung deckt aber ausschließlich den reinen Prose-Content ab und nicht automatisch die editorspezifischen Zusatz-Styles, weshalb Cursor-Farbe, Fokus-Ring und Platzhalter-Farbe im Dark Mode separat überprüft und bei Bedarf angepasst werden müssen.

Ein besonders leicht übersehener Punkt ist die Textauswahl-Farbe, die der Browser standardmäßig über ::selection rendert und die im hellen Modus meist ausreichend Kontrast bietet, im dunklen Modus aber schnell zu schwer lesbar wird. Eine explizite ::selection-Regel mit angepasster Hintergrundfarbe für den Dark-Mode-Kontext stellt sicher, dass markierter Text auch bei dunklem Editor-Hintergrund gut lesbar bleibt.

9. Typische Fallstricke: Preflight, ProseMirror-Basisklassen und Fokus-Ring

Tailwinds Preflight-Reset entfernt standardmäßig Browser-Default-Styles für Ueberschriften, Listen und Zitate, was in Kombination mit ProseMirrors eigenen Basisklassen zu doppelten oder widersprüchlichen Regeln führen kann, wenn beide Systeme versuchen, dieselben Elemente zu stylen. In der Praxis empfiehlt es sich, konsequent auf die prose-Klasse als einzige Quelle für typografische Basisregeln zu setzen und ProseMirrors eigene Klassen nicht zusätzlich manuell zu überschreiben, um Spezifitätskonflikte zu vermeiden.

Ein weiterer häufiger Fehler ist ein fehlender oder zu dezenter Fokus-Ring auf dem gesamten editierbaren Bereich, wodurch Nutzern, insbesondere Tastaturnutzern, nicht klar ist, ob der Editor tatsächlich den Eingabefokus besitzt. Statt focus:outline-none ersatzlos zu setzen, sollte immer eine alternative, deutlich sichtbare Fokus-Markierung ergänzt werden, etwa ein farbiger Rahmen über focus-within:ring-2 auf dem umschließenden Editor-Container.

Bereich Zuständige Klasse/API Typische Anpassung Wichtiger Hinweis
Fließtext im Editor prose (Typography-Plugin) prose-slate, max-w-none, Innenabstand Basis für Editor und Render-Ansicht gleichermaßen
Toolbar-Button aktiv editor.isActive('format') bg-slate-800, text-white bei aktiv aria-pressed für Barrierefreiheit setzen
Platzhalter Placeholder-Extension + ::before color: theme(colors.slate.400) Gleiche Schriftgröße wie echter Text
Task-Listen/Code-Blöcke prose-code:, prose-a: Modifier Eigene Farb- und Layoutregeln Standard-prose deckt diese Elemente nicht ab
Fokus-Zustand focus-within: auf Editor-Container ring-2, ring-sky-500 Nie focus:outline-none ohne Alternative

Mironsoft

Tailwind-CSS-Architektur, Design-Systeme und Performance

Tailwind-Frontends, die trotz tausender Utility-Klassen wartbar bleiben?

Wir prüfen bestehende Tailwind-Projekte auf aufgeblähte Klassenlisten, inkonsistente Design-Tokens und ungenutzte CSS-Reste und bauen daraus ein Design-System, das sich sauber skaliert statt mit jeder Komponente unübersichtlicher zu werden.

Design-System-Review

Tokens, Spacing-Skala und Komponentenkonsistenz auf Wartbarkeit prüfen.

Performance-Optimierung

CSS-Bundle-Größe, Purge-Konfiguration und Ladezeiten systematisch reduzieren.

Component-Architektur

Wiederverwendbare, gut strukturierte Komponenten statt Klassenlisten-Wildwuchs aufbauen.

10. Zusammenfassung

Rich-Text-Editor-Styling mit TipTap: Das Wichtigste auf einen Blick

Grundprinzip

TipTap liefert keine eigenen Styles, die prose-Klasse aus @tailwindcss/typography liefert die typografische Basis.

Editor vs. Render

Beide Ansichten teilen dieselbe prose-Basis, editorspezifische Regeln kommen über eine zusätzliche Modifier-Klasse dazu.

Toolbar-Zustand

editor.isActive('format') liefert den booleschen Zustand für visuelles Feedback, unabhängig vom eigentlichen Formatierungsbefehl.

Häufigste Fallen

Preflight/ProseMirror-Konflikte, fehlender Fokus-Ring nach focus:outline-none, zu starker Platzhalter-Kontrast.

11. FAQ: Rich-Text-Editor-Styling mit TipTap: Das Wichtigste auf einen Blick

1Warum sieht mein TipTap-Editor komplett ungestylt aus?
TipTap liefert bewusst keine eigenen visuellen Styles mit. Ohne eine CSS-Klasse wie prose aus dem @tailwindcss/typography-Plugin bleiben Ueberschriften, Listen und Zitate optisch identisch zu normalem Fließtext.
2Wie aktiviere ich die prose-Klasse im TipTap-Editor?
Ueber die editorProps.attributes.class-Konfiguration beim Erstellen des Editors, in der prose zusammen mit weiteren Utility-Klassen wie max-w-none und Innenabstand angegeben wird.
3Muss ich Editor-Ansicht und Render-Ansicht unterschiedlich stylen?
Die typografische Basis sollte identisch bleiben. Nur editorspezifische Details wie Cursor-Farbe, Fokus-Ring und Platzhalter-Text brauchen zusätzliche Regeln, die nur im Editor-Kontext gelten.
4Wie erkenne ich, ob ein Toolbar-Button gerade aktiv sein soll?
Mit editor.isActive('format'), zum Beispiel editor.isActive('bold'), das einen booleschen Zustand liefert, der in eine bedingte Klassenliste für den Button einfließt.
5Wie funktioniert der Platzhalter-Text in einem contenteditable-Editor?
Ueber TipTaps Placeholder-Extension, die ein ::before-Pseudo-Element auf dem ersten leeren Absatz einfügt. Anders als bei input-Feldern gibt es keinen nativen Platzhalter-Mechanismus für contenteditable.
6Deckt die Standard-prose-Klasse alle TipTap-Elemente ab?
Nein, editorspezifische Elemente wie Task-Listen mit Checkboxen oder Code-Blöcke mit Syntax-Highlighting brauchen eigene Zusatzregeln, meist über prose-*-Modifier-Utilities.
7Wie aktiviere ich Dark Mode für den Editor?
Mit prose-invert aus dem Typography-Plugin für den reinen Textinhalt. Cursor-Farbe, Fokus-Ring und Textauswahl-Farbe müssen im Dark Mode zusätzlich manuell angepasst werden.
8Warum sollte ich focus:outline-none nicht ohne Alternative setzen?
Ohne sichtbaren Fokus-Zustand können Nutzer, besonders Tastaturnutzer, nicht erkennen, ob der Editor den Eingabefokus besitzt. Eine Alternative wie focus-within:ring-2 sollte immer ergänzt werden.
9Warum kollidieren Tailwinds Preflight-Reset und ProseMirrors Basisklassen manchmal?
Beide Systeme versuchen, dieselben Elemente wie Ueberschriften und Listen zu stylen. Konsequentes Setzen auf die prose-Klasse als einzige Quelle für typografische Basisregeln vermeidet Spezifitätskonflikte.
10Sollte die Toolbar bei schmalen Bildschirmen umbrechen oder scrollen?
Horizontales Scrollen ist meist die bessere Wahl, weil umgebrochene Toolbar-Zeilen wertvollen vertikalen Platz für den eigentlichen Editierbereich kosten.