Komponenten · Barrierefreiheit nach Framework

Barrierefreiheit mit Tailwind CSS

Tailwind ist kein JavaScript-Framework, sondern CSS – und erzeugt trotzdem wiederkehrende Barrieren, weil es Entscheidungen bequem macht, die man nicht bequem treffen sollte. Die drei häufigsten: der mit outline-none entfernte Fokusring, zu helle Grautöne aus der Standardpalette und Animationen ohne Rücksicht auf prefers-reduced-motion. Alle drei sind Konfigurationsfragen, keine Grenzen des Werkzeugs.

Diese Seite behandelt die Tailwind-spezifischen Fälle. Die vier Probleme, die in jedem Framework gleich sind, stehen unter Barrierefreiheit nach Framework.

Das Wichtigste in Kürze

  • outline-none ohne Ersatz ist der häufigste Tailwind-Befund überhaupt – und ein klarer Verstoß gegen WCAG 2.4.7 (Stufe A).
  • focus-visible: statt focus: zeigt den Ring nur bei Tastaturbedienung – der Kompromiss, der beide Seiten zufriedenstellt.
  • sr-only ist korrekt umgesetzt und der richtige Weg für versteckte Beschriftungen; hidden und invisible sind es nicht.
  • Die Standardpalette ist nicht kontrastsicher. text-gray-400 auf Weiß liegt deutlich unter 4,5:1.
  • motion-safe: und motion-reduce: setzen prefers-reduced-motion direkt in Utility-Klassen um.
  • Utility-Klassen ersetzen keine Semantik: Ein div mit cursor-pointer bleibt ein div.
  • Der Dark Mode verdoppelt die Kontrastprüfung – beide Themes müssen bestehen.
  • Stand August 2026.

Der Fokusring: das eine große Thema

Kein anderes Werkzeug macht es so leicht, den Fokusring zu entfernen: outline-none ist eine Klasse. Und weil der Standard-Ring in vielen Designs stört, steht sie in sehr vielen Tailwind-Projekten.

Das Ergebnis ist ein Verstoß gegen 2.4.7 Fokus sichtbar auf Stufe A – und praktisch eine Anwendung, die mit der Tastatur unbenutzbar ist, weil niemand mehr sieht, wo er steht.

<!-- Falsch: Fokus ersatzlos entfernt -->
<button class="outline-none rounded bg-blue-600 px-4 py-2 text-white">
  Speichern
</button>

<!-- Richtig: eigener Ring, nur bei Tastaturbedienung -->
<button
  class="rounded bg-blue-600 px-4 py-2 text-white
         focus-visible:outline-none
         focus-visible:ring-2 focus-visible:ring-offset-2
         focus-visible:ring-blue-900">
  Speichern
</button>

Zwei Details machen den Unterschied. focus-visible: statt focus: blendet den Ring nur ein, wenn der Fokus über die Tastatur kam – das ist der Grund, aus dem der Ring ursprünglich entfernt wurde, und er entfällt damit. Und ring-offset-2 setzt den Ring mit Abstand um das Element, sodass er auf gleichfarbigem Hintergrund nicht verschwindet.

Wer den Ring einmal zentral definiert – in Tailwind über eine Komponentenklasse oder eine @layer base-Regel für :focus-visible –, hat das Thema für das ganze Projekt erledigt. Was ein brauchbarer Fokusindikator leistet, steht unter Tastaturbedienung & sichtbarer Fokus.

sr-only: richtig gut, oft falsch benutzt

Die Klasse sr-only ist eine korrekt umgesetzte Versteck-Technik: Das Element bleibt im Accessibility Tree, ist aber optisch nicht sichtbar. Genau das braucht man für Beschriftungen ohne sichtbaren Text.

<!-- Suchfeld mit Lupensymbol: das Label ist da, nur unsichtbar -->
<label for="suche" class="sr-only">Suchbegriff</label>
<input id="suche" type="search" class="rounded border px-3 py-2" />

<!-- Icon-Button: der Name kommt aus dem versteckten Text -->
<button class="rounded p-2">
  <svg aria-hidden="true" class="h-5 w-5">…</svg>
  <span class="sr-only">Menü öffnen</span>
</button>

Die zwei häufigen Fehler: hidden statt sr-only entfernt das Element aus dem Baum – dann ist auch der Name weg. Und sr-only an einem fokussierbaren Element erzeugt einen Tab-Stopp, den niemand sieht; dafür gibt es focus:not-sr-only, das etwa Skip-Links bei Fokus sichtbar macht.

Wann ein verstecktes Label und wann aria-label die richtige Wahl ist, steht unter Labels & Beschriftungen.

Die Standardpalette ist nicht kontrastsicher

Tailwinds Grauabstufungen sind gleichmäßig verteilt, nicht kontrastgeprüft. Auf weißem Grund liegt Fließtext erst ab etwa gray-600 sicher über 4,5:1; die beliebten gray-400- und gray-500-Töne für Hilfetexte, Platzhalter und deaktivierte Zustände liegen darunter.

Praktische Konsequenzen:

  • Platzhaltertext ist kontrastpflichtig, sobald er Information trägt – ein Grund mehr, ihn nicht als Label zu missbrauchen.
  • Deaktivierte Bedienelemente sind von der Kontrastanforderung ausgenommen, aber wenn niemand mehr lesen kann, was deaktiviert ist, hilft die Ausnahme keinem.
  • Rahmen und Trennlinien um Bedienelemente brauchen 3:1 nach 1.4.11border-gray-200 reicht dafür nicht.

Der wirksame Schritt ist, im eigenen Theme nur geprüfte Werte freizugeben und die kritischen Töne gar nicht erst anzubieten. Die Werte prüfst du mit dem Kontrast-Check, die Systematik steht unter Farbkontraste.

Im Dark Mode verdoppelt sich die Prüfung: Jede Farbkombination muss in beiden Themes bestehen, und hell auf dunkel verhält sich nicht spiegelbildlich – ein Ton, der auf Weiß knapp besteht, fällt invertiert oft durch.

Bewegung: motion-safe und motion-reduce

Tailwind bringt die Medienabfrage als Variante mit, was sie praktisch kostenlos macht:

<!-- Animation nur, wenn Bewegung nicht abbestellt wurde -->
<div class="motion-safe:animate-pulse rounded bg-gray-200 p-4">Lädt …</div>

<!-- Oder umgekehrt: Übergang bei reduzierter Bewegung abschalten -->
<div class="transition-transform duration-300 motion-reduce:transition-none">…</div>

Die Faustregel: motion-safe: für alles, was sich von selbst bewegt, motion-reduce: für Übergänge, die bleiben dürfen, aber kürzer sein sollten. Wen das betrifft, steht unter Kognition & Neurodiversität.

Ein kontrastsicheres Theme aufsetzen

Der wirksamste Schritt ist, die problematischen Töne gar nicht erst zur Verfügung zu stellen. Statt die Standardpalette zu erweitern, definiert man eine kleine, geprüfte Menge semantischer Farben – dann ist jede Klasse, die jemand schreiben kann, bereits geprüft:

// tailwind.config.js – geprüfte Werte statt der vollen Palette
export default {
  theme: {
    extend: {
      colors: {
        text: {
          DEFAULT: '#1f2933',   // 14,5:1 auf Weiß
          muted:   '#52606d',   // 7,1:1 auf Weiß – auch für Hilfetexte sicher
        },
        // bewusst kein "text-light": alles darüber fällt durch
        rand: '#7b8794',        // 3,4:1 – reicht für 1.4.11
      },
    },
  },
};

Zwei Regeln haben sich dabei bewährt. Namen nach Zweck, nicht nach Helligkeit: Wer text-muted schreibt, denkt über die Rolle nach; wer text-gray-400 schreibt, über den Farbton. Und kein Ton unter der Schwelle im Theme: Was nicht existiert, kann niemand versehentlich benutzen. Die Werte prüfst du vorher einmal mit dem Kontrast-Check.

Für den Dark Mode gilt dasselbe zweimal – am einfachsten über CSS-Variablen, die beide Themes bedienen, statt über dark:-Varianten an jeder einzelnen Klasse.

Die drei Dinge, die ein Review immer prüfen sollte

Ein Tailwind-Review braucht keine lange Liste. Drei Suchen im Projekt decken den Großteil ab:

  1. outline-none ohne begleitendes focus-visible:ring – der Verstoß gegen 2.4.7, und der einzige, der sich per Textsuche zuverlässig finden lässt.
  2. cursor-pointer an einem Element, das kein <a> oder <button> ist – fast immer ein nachgebautes Bedienelement.
  3. text-gray-400, text-gray-300 und placeholder-gray-400 auf hellem Grund – die Kontrastfälle, die im Entwurf niemandem auffallen.

Alle drei sind mit grep zu finden und in Minuten zu beheben – deutlich billiger als derselbe Befund in einem Prüfbericht.

Die Klassen, die Semantik entfernen

Zwei Utility-Klassen verändern nicht nur die Darstellung, sondern das, was Hilfsmittel melden. Das ist die unangenehmste Kategorie, weil im Code alles richtig aussieht.

list-none setzt list-style: none. In WebKit – also Safari am Mac und alle Browser auf iOS – entfernt das die Listensemantik: VoiceOver sagt „Liste mit 5 Einträgen“ dann nicht mehr an. Bei einer Navigation ist das der Unterschied zwischen „ich weiß, wie viele Punkte kommen“ und „ich taste mich durch“. Der Ausweg ist eine explizite Rolle:

<!-- In WebKit ohne Listensemantik -->
<ul class="list-none">…</ul>

<!-- Semantik zurückgeholt -->
<ul role="list" class="list-none">…</ul>

Das gilt für jede Liste, die ihre Aufzählungszeichen verliert – Navigationen, Karten-Raster, Tag-Leisten. Warum Listen überhaupt zählen, steht unter Listen richtig nutzen.

appearance-none entfernt die native Darstellung von Formularelementen. Die Semantik bleibt zwar, aber Zustände verschwinden optisch: Eine Checkbox mit appearance-none und ohne eigenes Häkchen ist nicht mehr erkennbar an oder aus – ein Verstoß gegen 1.4.1 Benutzung von Farbe, sobald nur noch die Füllfarbe den Zustand trägt. Die Umsetzung steht unter Toggle-Switches & Custom-Checkboxen.

Häufiger Fehler in der Praxis

cursor-pointer statt <button>. Ein div sieht damit klickbar aus und ist es für Tastatur und Screenreader trotzdem nicht – siehe Buttons vs. Links.

Überschriftenebene nach Schriftgröße wählen. In Tailwind ist die Größe eine Klasse, also frei wählbar. Genau deshalb rutscht die Ebene gern nach Optik statt nach Struktur – siehe Überschriften-Hierarchie.

hidden für Aufklapp-Inhalte. Funktioniert, entfernt aber das Element vollständig. Für ein Akkordeon ist das in Ordnung, für ein Element, das den Fokus hält, nicht.

focus: statt focus-visible:. Der Ring erscheint dann auch beim Mausklick – und genau das führt in der nächsten Designrunde dazu, dass er wieder entfernt wird.

Häufige Fragen

Ist Tailwind schlecht für Barrierefreiheit?

Nein. Es ist CSS und verhält sich neutral – es macht nur bestimmte Fehler bequemer als andere Ansätze. Ein einmal zentral definierter Fokusstil und ein geprüftes Theme nehmen den größten Teil des Risikos.

Wie erzwinge ich einen Fokusring projektweit?

Über eine Basisregel für :focus-visible in @layer base, ergänzt um ein Lint- oder Review-Verbot für outline-none ohne begleitendes focus-visible:ring-*.

Reicht sr-only für alle versteckten Texte?

Für Texte, die Hilfsmittel hören sollen: ja. Für Inhalte, die niemand bekommen soll: nein – dafür ist hidden richtig. Der Unterschied wird unter Screenreader-Grundlagen erklärt.

Gilt das auch für andere Utility-CSS-Ansätze?

Ja, weitgehend. Die Befunde hängen nicht an Tailwind, sondern daran, dass Utility-Klassen Darstellung ohne Semantik beschreiben – und dass der Fokusring eine Klasse entfernt.

Verwandte Themen

Quellen

Gratis E-Book PDF Das Praxishandbuch

Kostenloses E-Book

Das Praxishandbuch für sauberes, zugängliches Web

Alles rund um semantisches HTML, Barrierefreiheit, WCAG & BFSG, GEO und SEO — praxisnah und am echten Code. In mehreren Feedbackschleifen von Leserinnen und Lesern verbessert.

  • 3.000+ Downloads
  • 7. Auflage
  • 37 Seiten
  • PDF

Kein Spam. Abmeldung jederzeit mit einem Klick möglich.