Side-by-Side und Inline, konsistente Farbcodierung und Zeilennummern mit Tailwind CSS
Eine Diff-Ansicht muss auf einen Blick zeigen, was hinzugefügt, entfernt oder geändert wurde, egal ob als Side-by-Side-Vergleich zweier Spalten oder als kompaktere Inline-Darstellung. Dieses Muster zeigt, wie sich beide Varianten mit Tailwind CSS konsistent einfärben, mit Zeilennummern versehen und an einem praktischen Textvergleich zwischen zwei Versionen durchspielen lassen.
Inhaltsverzeichnis
- 1. Warum eine Diff-Ansicht mehr als drei Farben braucht
- 2. Side-by-Side- und Inline-Layout mit Tailwind Grid
- 3. Konsistente Farbcodierung über die gesamte Ansicht hinweg
- 4. Die Zeilennummern-Spalte richtig ausrichten
- 5. Praxisbeispiel: zwei Textversionen vergleichen
- 6. Diff-Farben im Dark Mode anpassen
- 7. Barrierefreiheit: nicht nur auf Farbe verlassen
- 8. Lange Zeilen: Umbruch statt horizontalem Scrollen
- 9. Grenzen des Musters und typische Fehler
- 10. Zusammenfassung
- 11. FAQ
1. Warum eine Diff-Ansicht mehr als drei Farben braucht
Eine Diff-Ansicht vergleicht zwei Versionen eines Inhalts, meist Text oder Code, und muss dabei drei grundsätzlich unterschiedliche Änderungsarten unterscheidbar machen: hinzugefügte Zeilen, entfernte Zeilen und geänderte Zeilen, bei denen ein Teil des Inhalts erhalten bleibt und ein anderer Teil ersetzt wird. Werden alle drei Zustände nur über eine einzige Akzentfarbe unterschieden, etwa alle drei blau mit unterschiedlicher Deckkraft, muss die Nutzerin den Zeileninhalt selbst lesen, um die Änderungsart zu verstehen, was den eigentlichen Zweck einer Diff-Ansicht untergräbt.
Die etablierte Konvention aus Versionskontrollsystemen wie Git ist Grün für Hinzugefügtes, Rot für Entferntes und eine dritte Farbe, meist Gelb oder Orange, für Geändertes oder für hervorgehobene Wortbereiche innerhalb einer ansonsten unveränderten Zeile. Diese Konvention sollte nicht ohne guten Grund gebrochen werden, da Entwicklerinnen und technisch versierte Nutzerinnen sie aus Terminal, IDE und Pull-Request-Ansichten bereits verinnerlicht haben und eine abweichende Farbwahl unnötige kognitive Reibung erzeugt.
2. Side-by-Side- und Inline-Layout mit Tailwind Grid
Die Side-by-Side-Ansicht stellt die alte und die neue Version nebeneinander in zwei Spalten dar, wobei entfernte Zeilen nur links und hinzugefügte Zeilen nur rechts erscheinen, während unveränderte Zeilen auf beiden Seiten auf gleicher Höhe stehen. Mit CSS Grid und zwei gleich breiten Spalten über grid-cols-2 lässt sich das sauber umsetzen, wichtig ist dabei, dass leere Platzhalterzeilen auf der jeweils anderen Seite eingefügt werden, damit die Zeilen trotz unterschiedlicher Länge der beiden Versionen synchron auf gleicher Höhe bleiben.
Die Inline-Ansicht dagegen zeigt alle Zeilen untereinander in einer einzigen Spalte, in der Reihenfolge, in der sie in der zusammengeführten Diff-Berechnung erscheinen. Sie braucht deutlich weniger horizontalen Platz und eignet sich daher besser für schmale Bildschirme oder eingebettete Vorschauen, verliert dabei aber den direkten visuellen Vergleich auf gleicher Zeilenhöhe, den die Side-by-Side-Ansicht bietet. In der Praxis bewährt sich ein Umschalter zwischen beiden Modi, oft per Alpine-Boolean gesteuert, damit Nutzerinnen je nach Bildschirmbreite und Vorliebe wählen können.
<div x-data="{ mode: 'side-by-side' }">
<div class="mb-3 flex gap-2 text-sm">
<button @click="mode = 'side-by-side'" :class="mode === 'side-by-side' && 'font-semibold text-blue-700'">
Side-by-Side
</button>
<button @click="mode = 'inline'" :class="mode === 'inline' && 'font-semibold text-blue-700'">
Inline
</button>
</div>
<div x-show="mode === 'side-by-side'" class="grid grid-cols-2 divide-x divide-slate-200 font-mono text-sm">
<div class="bg-red-50">
<div class="flex px-2 py-0.5">
<span class="w-8 shrink-0 select-none text-right text-slate-400">12</span>
<span class="pl-3 text-red-800">- return $legacyPrice;</span>
</div>
</div>
<div class="bg-green-50">
<div class="flex px-2 py-0.5">
<span class="w-8 shrink-0 select-none text-right text-slate-400">12</span>
<span class="pl-3 text-green-800">+ return $priceFormatter->format($price);</span>
</div>
</div>
</div>
<div x-show="mode === 'inline'" class="divide-y divide-slate-100 font-mono text-sm">
<div class="flex bg-red-50 px-2 py-0.5">
<span class="w-8 shrink-0 select-none text-right text-slate-400">12</span>
<span class="pl-3 text-red-800">- return $legacyPrice;</span>
</div>
<div class="flex bg-green-50 px-2 py-0.5">
<span class="w-8 shrink-0 select-none text-right text-slate-400">12</span>
<span class="pl-3 text-green-800">+ return $priceFormatter->format($price);</span>
</div>
</div>
</div>
3. Konsistente Farbcodierung über die gesamte Ansicht hinweg
Damit die Farbcodierung wirklich als verlässliches Signal funktioniert, muss sie an jeder Stelle der Diff-Ansicht identisch verwendet werden: dieselbe Grün-Nuance für den Zeilenhintergrund, die Zeilennummer, ein eventuelles Plus-Symbol am Zeilenanfang und einen möglichen Zähler in der Zusammenfassung über der Diff-Ansicht. Ein Bruch in dieser Konsistenz, etwa ein kräftigeres Grün im Hintergrund als im Zähler, lässt die Nutzerin unbewusst zweifeln, ob es sich tatsächlich um dieselbe Kategorie handelt.
Praktisch bewährt sich eine feste Zuordnung von Tailwind-Farbstufen pro Zustand, etwa green-50 für den Zeilenhintergrund und green-800 für den Text bei Hinzugefügtem, spiegelbildlich red-50 und red-800 für Entferntes. Diese Zuordnung sollte zentral als Konstante oder Tailwind-Komponente definiert werden, statt in jeder einzelnen Diff-Zeile erneut die passenden Klassen von Hand zusammenzustellen, denn eine spätere Anpassung der Farbpalette betrifft sonst potenziell Dutzende Stellen im Code.
4. Die Zeilennummern-Spalte richtig ausrichten
Zeilennummern müssen rechtsbündig ausgerichtet werden, damit einstellige und mehrstellige Nummern trotz unterschiedlicher Zeichenanzahl an derselben horizontalen Position enden und der Diff-Inhalt selbst in einer geraden Linie beginnt. Eine feste Spaltenbreite über w-8 oder w-10, je nachdem wie viele Zeilen die längste erwartete Datei hat, verhindert außerdem, dass die Spalte bei besonders langen Dateien mit vierstelligen Zeilennummern plötzlich breiter wird als der Rest der Ansicht.
Bei der Side-by-Side-Ansicht braucht jede der beiden Spalten eine eigene, unabhängige Zeilennummerierung, da die alte und die neue Version unterschiedlich viele Zeilen haben können und eine hinzugefügte Zeile in der alten Version schlicht keine entsprechende Nummer hat. Für Zeilen ohne Entsprechung auf der jeweils anderen Seite bleibt die Zeilennummer leer, statt eine Null oder einen Platzhalter wie einen Bindestrich anzuzeigen, was in Diff-Werkzeugen wie Git leicht mit einer echten Zeilennummer verwechselt werden könnte.
5. Praxisbeispiel: zwei Textversionen vergleichen
Ein einfacher, aber lehrreicher Anwendungsfall ist der Vergleich zweier kurzer Textabsätze, etwa zwei Versionen eines Produktbeschreibungstextes vor und nach einer redaktionellen Überarbeitung. Der Diff-Algorithmus selbst, üblicherweise eine Zeilen- oder Wort-basierte Longest-Common-Subsequence-Berechnung, liefert eine Liste von Operationen: unverändert, hinzugefügt, entfernt. Diese Liste wird dann einfach zeilenweise mit den entsprechenden Tailwind-Klassen aus der Farbcodierung gerendert, ohne dass die Darstellung selbst irgendeine Diff-Logik enthalten müsste.
Für Textvergleiche auf Wortebene statt reiner Zeilenebene lohnt sich eine zusätzliche Hervorhebung einzelner geänderter Wörter innerhalb einer ansonsten identischen Zeile, etwa über ein mark-Element mit gelbem Hintergrund. Das macht kleine Änderungen wie eine korrigierte Zahl oder ein ausgetauschtes Adjektiv deutlich sichtbarer, als wenn die komplette Zeile pauschal als geändert markiert würde, was bei überwiegend identischen langen Zeilen die eigentliche Änderung leicht untergehen lässt.
6. Diff-Farben im Dark Mode anpassen
Die hellen Hintergrundfarben wie green-50 und red-50, die im Light Mode gut funktionieren, wirken im Dark Mode auf einem dunklen Hintergrund wie ein greller Fremdkörper, wenn sie unverändert übernommen werden. Über Tailwinds dark:-Präfix lassen sich für jeden Zustand dunklere, gesättigtere Hintergrundtöne definieren, etwa dark:bg-green-950 und dark:bg-red-950, kombiniert mit entsprechend helleren Textfarben wie dark:text-green-300, damit der Kontrast in beiden Modi vergleichbar bleibt.
Wichtig ist, die Farbzuordnung zwischen Light und Dark Mode konsistent zu halten, sodass Grün immer Grün bleibt und nicht etwa im Dark Mode zu einem anderen Farbton wechselt, nur weil ein bestimmter Grünton im Dunkeln schlecht lesbar war. Eine kleine Testrunde mit realistischen Diff-Inhalten in beiden Modi deckt außerdem zuverlässiger auf, ob der Kontrast tatsächlich ausreicht, als eine rein rechnerische Kontrastprüfung einzelner Farbpaare.
7. Barrierefreiheit: nicht nur auf Farbe verlassen
Wer Farben nicht unterscheiden kann, etwa bei Rot-Grün-Farbenblindheit, die einen relevanten Anteil der männlichen Bevölkerung betrifft, darf sich in einer Diff-Ansicht nicht ausschließlich auf die Hintergrundfarbe verlassen müssen, um Hinzugefügtes von Entferntem zu unterscheiden. Ein Plus- und ein Minus-Symbol am Zeilenanfang, wie es die meisten Terminal-Diffs ohnehin schon verwenden, liefert ein zweites, farbunabhängiges Signal und sollte in jeder Diff-Ansicht standardmäßig vorhanden sein, nicht nur als optionales Extra.
Für Screenreader-Nutzerinnen reicht die visuelle Kennzeichnung allein ohnehin nicht aus. Jede Diff-Zeile sollte über ein aria-label oder einen versteckten Text kommunizieren, ob es sich um eine hinzugefügte, entfernte oder unveränderte Zeile handelt, etwa aria-label="Hinzugefügte Zeile" auf dem umschließenden Element. Ohne diese textuelle Ergänzung bleibt der komplette Sinn der Diff-Ansicht für Screenreader-Nutzerinnen verborgen, selbst wenn Farbe und Symbol für sehende Nutzerinnen eindeutig sind.
8. Lange Zeilen: Umbruch statt horizontalem Scrollen
Code-Zeilen oder lange Textsätze überschreiten schnell die verfügbare Breite der Diff-Spalte, besonders in der Side-by-Side-Ansicht, wo jede Spalte nur die Hälfte der Gesamtbreite zur Verfügung hat. Horizontales Scrollen pro Zeile ist technisch möglich, führt aber dazu, dass Nutzerinnen bei jeder langen Zeile erneut scrollen müssen, um den vollständigen Inhalt zu sehen, was bei einem Diff mit vielen langen Zeilen schnell mühsam wird.
Ein automatischer Zeilenumbruch über whitespace-pre-wrap statt whitespace-pre löst dieses Problem, erzeugt dafür aber Zeilen unterschiedlicher visueller Höhe, was in der Side-by-Side-Ansicht die exakte Zeilen-zu-Zeilen-Ausrichtung zwischen linker und rechter Spalte erschwert. In der Praxis ist ein Kompromiss üblich: automatischer Umbruch in der Inline-Ansicht, wo die exakte Zeilenhöhe keine Rolle spielt, und horizontales Scrollen mit einer sichtbaren Scrollbar in der Side-by-Side-Ansicht, um die Ausrichtung zu erhalten.
9. Grenzen des Musters und typische Fehler
Ein häufiger Fehler ist, die Diff-Berechnung selbst clientseitig für sehr große Dateien durchzuführen, etwa eine komplette generierte Datei mit zehntausenden Zeilen. Ein naiver Diff-Algorithmus hat in solchen Fällen eine Laufzeit, die den Browser-Tab für mehrere Sekunden blockieren kann, weshalb sich für große Vergleiche eine serverseitige Berechnung oder zumindest ein Web Worker lohnt, der die Berechnung außerhalb des Haupt-Threads durchführt, während die UI weiterhin reagiert.
Eine zweite Grenze betrifft reine Whitespace-Änderungen, etwa eine geänderte Einrückung ohne inhaltliche Änderung, die in einer naiven zeilenbasierten Diff-Berechnung als vollständig geänderte Zeile erscheinen, obwohl inhaltlich nichts Wesentliches passiert ist. Ein Umschalter, der Whitespace-Änderungen optional ignoriert, ähnlich der Option in Git-Diffs, reduziert in solchen Fällen erheblich das visuelle Rauschen und lässt die tatsächlich relevanten Änderungen deutlicher hervortreten.
| Zustand | Hintergrund (Light) | Hintergrund (Dark) | Zusätzliches Signal |
|---|---|---|---|
| Hinzugefügt | bg-green-50, text-green-800 | bg-green-950, text-green-300 | Plus-Symbol am Zeilenanfang |
| Entfernt | bg-red-50, text-red-800 | bg-red-950, text-red-300 | Minus-Symbol am Zeilenanfang |
| Geändert (Wortebene) | bg-yellow-100 auf mark | bg-yellow-900/40 auf mark | Hervorhebung nur des geänderten Wortbereichs |
| Unverändert | bg-transparent | bg-transparent | Kein zusätzliches Symbol nötig |
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
Diff-Vergleichsansicht mit Tailwind: Das Wichtigste auf einen Blick
Farbkonvention
Grün für hinzugefügt, Rot für entfernt, Gelb für geändert, konsistent über Hintergrund, Text und Zusammenfassung hinweg.
Side-by-Side vs. Inline
Zwei Grid-Spalten mit Platzhalterzeilen gegen eine einzelne Spalte in Diff-Reihenfolge, oft per Umschalter wählbar.
Barrierefreiheit
Plus-/Minus-Symbole und aria-label ergänzen die Farbcodierung, damit Farbenblinde und Screenreader-Nutzerinnen nicht ausgeschlossen werden.
Lange Zeilen
whitespace-pre-wrap in der Inline-Ansicht, horizontales Scrollen mit erhaltener Zeilen-Ausrichtung in der Side-by-Side-Ansicht.