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.
Inhaltsverzeichnis
- 1. TipTap und ProseMirror: warum die Bibliothek keine eigenen Styles mitbringt
- 2. Die prose-Klasse für den editierbaren Bereich anpassen
- 3. Unterschied zwischen Editor-Ansicht und finaler Render-Ansicht
- 4. Toolbar-Buttons mit aktivem und inaktivem Zustand stylen
- 5. Platzhalter-Text-Styling mit der Placeholder-Extension
- 6. Eigene prose-Overrides für Editor-spezifische Elemente
- 7. Praxisbeispiel: eine vollständige Editor-Komponente mit Toolbar und Statusleiste
- 8. Dark Mode: prose-invert und die Editor-Cursor-Farbe
- 9. Typische Fallstricke: Preflight, ProseMirror-Basisklassen und Fokus-Ring
- 10. Zusammenfassung
- 11. FAQ
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.