Der Fokus-Indikator ist für Tastaturnutzende das, was der Mauszeiger für alle anderen ist: die einzige Orientierung auf der Seite. Er wird entfernt, weil die Browser-Standarddarstellung ästhetisch unbefriedigend wirkt. Dieser Snippet zeigt, wie man ihn richtig ersetzt.
/* Steht in sehr vielen Stylesheets */
*:focus {
outline: none;
}
Diese Zeile ist ein Barrierefreiheitsfehler. Sie entfernt für alle fokussierten Elemente jeden sichtbaren Hinweis auf den aktuellen Fokus. WCAG 2.4.7 (Focus Visible, Level AA) verlangt, dass Tastaturfokus sichtbar ist. Mit dieser Regel ist er es nicht.
:focus greift bei jeder Art von Fokus: Mausklick, Tastatur, Touch. :focus-visible greift nur dann, wenn der Browser einschätzt, dass der Nutzer von einer sichtbaren Markierung profitiert. In der Praxis: bei Tastaturnavigation, nicht bei Mausklick.
Das robuste Muster nimmt den Fokus-Ring nur dort weg, wo :focus-visible nicht greift:
/* Fokus-Ring nur bei Tastaturnavigation weg */
:focus:not(:focus-visible) {
outline: none;
}
/* Eigener Ring für Tastaturnavigation */
:focus-visible {
outline: 3px solid var(--focus-ring-outer);
outline-offset: 3px;
}
Ein globales :focus{outline:none} ohne diesen Vorbehalt ist genau die Denkfigur, die das Problem erst erzeugt. Browser wenden :focus-visible-Logik auch bei Textfeldern an, wo der Zustand sichtbar bleiben soll, selbst wenn der Fokus per Maus gesetzt wurde. MDN weist darauf hin, dass Browser-Heuristiken hier kontextabhängig entscheiden.
Drei Kriterien sind für selbst gestaltete Fokus-Indikatoren einschlägig.
WCAG 2.4.7 Focus Visible (Level AA): Der Tastaturfokus muss sichtbar sein. Das ist die Grundanforderung.
WCAG 1.4.11 Non-text Contrast (Level AA): Wer einen eigenen Fokus-Indikator gestaltet, muss einen Kontrast von mindestens 3:1 zur angrenzenden Farbe sicherstellen. Ausgenommen sind nur Browser-Standardstile, die der Autor nicht verändert hat. Das ist das AA-Kriterium für Kontrast bei Fokus-Indikatoren.
WCAG 2.4.11 Focus Not Obscured Minimum (Level AA): Ein fokussiertes Element darf nicht vollständig durch überlagernde Inhalte verdeckt werden. Sticky Header, Cookie-Banner und Overlays sind typische Problemfälle. Dazu mehr im Abschnitt weiter unten.
WCAG 2.4.13 Focus Appearance (Level AAA): Definiert konkrete Mindestmaße: Fokus-Indikator-Fläche entspricht mindestens einer 2px-Umrandung des Elements, Kontrast mindestens 3:1 zwischen fokussiertem und unfokussiertem Zustand. Dieses Kriterium ist AAA, eignet sich aber hervorragend als Qualitätsmaßstab für Design-Systeme. Ein Ring von 3px mit deutlichem Kontrast trifft diesen Standard und übertrifft ihn.
Ein Fokus-Indikator sollte außerdem nicht nur eine leichte Farbveränderung sein. Ring, Unterstreichung oder eine kontrastierende Fläche macht den Zustand unabhängig von Farbnuancen erkennbar.
Ein Token-Set im Design-System steuert alle Fokus-Indikatoren konsistent. Für wechselnde Hintergründe, Bildkacheln oder dunkle Sections empfiehlt sich ein Zwei-Farben-Ring: Wenn die beiden Ringfarben mindestens 9:1 Kontrast zueinander aufweisen, ist garantiert, dass mindestens eine davon 3:1 zum Hintergrund erreicht. W3C Technique C40 beschreibt dieses Pattern explizit.
outline ist für Fokus-Indikatoren besser geeignet als border: Es nimmt keinen Platz im Layout ein und verursacht keine Verschiebungen des Seitenaufbaus, wenn ein Element fokussiert wird.
Für Textlinks im Fließtext, wo die Textfarbe bereits kontrastkonform ist, gibt es eine einfachere Alternative: currentColor sorgt dafür, dass die Outline automatisch dieselbe Farbe wie der Text erhält.
a:focus-visible {
outline: 3px solid currentColor;
outline-offset: 3px;
}
Auf Buttons mit heller Schrift auf dunklem Hintergrund funktioniert currentColor nicht zuverlässig. Dort braucht es den expliziten Zwei-Farben-Ring.
:root {
--focus-ring-inner: #ffffff;
--focus-ring-outer: #003366;
--focus-ring-width: 2px;
--focus-ring-offset: 2px;
}
:focus:not(:focus-visible) {
outline: none;
}
:focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-inner);
outline-offset: var(--focus-ring-offset);
box-shadow: 0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-ring-outer);
}
@media (forced-colors: active) {
:focus-visible {
outline: 3px solid Highlight;
box-shadow: none;
}
}
Der innere weiße Ring trennt den Fokus-Indikator vom Element, der äußere dunkle Ring sorgt für Sichtbarkeit auf hellen Hintergründen. Auf dunklem Hintergrund kehrt sich das Verhältnis um: Der weiße Ring wird zur Konturfarbe. Das forced-colors-Override schaltet auf Systemfarbe, weil box-shadow im Forced Colors Mode auf none gesetzt wird.
Der globale :focus-visible-Ring ist die Basis. Komponenten dürfen ihn anpassen, aber nur um ihn zu verbessern. Farben kommen aus dem globalen Token-Set, Komponenten steuern Geometrie: Offset und Radius.
Fokussierbar sind immer die interaktiven Elemente innerhalb einer Komponente, nicht die Container selbst. li:focus-visible greift nicht, weil li kein fokussierbares Element ist. li a:focus-visible oder .nav a:focus-visible schon.
/* Textlink im Fließtext */
a:focus-visible {
outline-offset: 3px;
}
/* Normaler Button */
.button:focus-visible {
outline-offset: 3px;
}
/* Icon-Button: kreisförmig, mehr Abstand */
.icon-button:focus-visible {
outline-offset: 6px;
border-radius: 999px;
}
/* Hauptnavigation */
.mainnav a:focus-visible {
outline-offset: 4px;
}
/* Card mit Link-Wrapper: Fokus umschließt die ganze Karte */
.card-link:focus-visible {
outline-offset: 8px;
border-radius: var(--radius-card, 4px);
}
/* Formularfelder: Radius übernehmen, kleinerer Offset */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline-offset: 2px;
border-radius: inherit;
}
/* Skip-Link: sichtbar im Viewport, nicht im normalen Fluss */
.skip-link:focus-visible {
position: fixed;
top: 1rem;
left: 1rem;
z-index: 9999;
outline-offset: 3px;
}
/* ARIA-Komponenten als Catch-all */
[role="tab"]:focus-visible,
summary:focus-visible,
.slider-control:focus-visible {
outline-offset: 3px;
}
box-shadow folgt dem border-radius und sieht bei gerundeten Elementen oft besser aus als outline. Es hat aber eine kritische Schwäche: Im Forced Colors Mode wird box-shadow auf none gesetzt. Ein Fokus-Indikator, der ausschließlich über box-shadow realisiert wird, verschwindet genau dann, wenn Sichtbarkeit am wichtigsten ist.
W3C Technique C40 hält deshalb fest: outline:none nicht setzen, nur um auf box-shadow allein zu wechseln. Wenn box-shadow gestalterisch nötig ist, bleibt eine Outline als Fallback nötig.
Der Zwei-Farben-Ring oben kombiniert beides sauber: outline als zuverlässige Basis, box-shadow als gestalterische Ergänzung, forced-colors-Block setzt box-shadow zurück und hält outline aktiv.
WCAG 2.4.11 verlangt, dass ein fokussiertes Element nicht vollständig von überlagernden Inhalten verdeckt wird. Sticky Header, Cookie-Banner, Offcanvas-Menüs und Chat-Widgets sind typische Problemfälle. Ein sauber gestylter Fokus-Ring hilft wenig, wenn er beim Scrollen unter dem Header verschwindet.
/* Scroll-Anker korrigieren: fokussierte Elemente landen
unterhalb des Sticky Headers */
html {
scroll-padding-top: calc(var(--header-height) + 1rem);
}
[id] {
scroll-margin-top: calc(var(--header-height) + 1rem);
}
Das löst nicht alle Overlays, ist aber der CSS-seitige Eingriff mit dem größten Effekt. Modale Dialoge müssen zusätzlich Fokus-Management betreiben, damit fokussierbare Elemente nicht hinter dem Modal erreichbar sind.
forced-colors:active signalisiert, dass das Betriebssystem eine eingeschränkte Farbpalette erzwingt. Windows Contrast Themes sind das häufigste Beispiel. Eigene Farben werden durch systemdefinierte Keywords überschrieben.
@media (forced-colors: active) {
:focus-visible {
outline: 3px solid Highlight;
box-shadow: none;
}
}
Highlight ist für generische Fokus-Indikatoren semantisch die bessere Wahl als ButtonText, weil es die Systemfarbe für ausgewählte bzw. aktive Zustände abbildet. Wichtig: forced-color-adjust:none sehr zurückhaltend einsetzen, weil es die Nutzerpräferenz bewusst außer Kraft setzt.
Zwei verwandte Pseudoklassen, die in bestimmten Situationen nützlich sind.
:focus-within greift auf einem Element, wenn es selbst oder eines seiner Kindelemente fokussiert ist, unabhängig davon, ob per Tastatur oder Maus. Typischer Einsatz: einen Navigations-Container hervorheben, solange ein Link darin fokussiert ist.
/* Menüpunkt-Container hervorheben, solange ein Link fokussiert ist */
.mainnav li:focus-within {
background: var(--color-nav-hover);
}
:has(:focus-visible) ist das modernere, präzisere Pendant: Es greift nur dann, wenn ein Kindelement per Tastatur fokussiert ist. Das fehlende focus-visible-within lässt sich damit sauber nachbauen.
/* Card hervorheben, wenn ein enthaltener Link per Tastatur fokussiert ist */
.card:has(:focus-visible) {
outline: var(--focus-ring-width) solid var(--focus-ring-outer);
outline-offset: 4px;
border-radius: var(--radius-card, 4px);
}
:has() wird seit 2023 von allen modernen Browsern unterstützt.
:focus-visible ist seit 2022 breit verfügbar. Wer ältere Browser absichern will, kann den progressiven Ansatz nutzen: erst einen einfachen :focus-Ring als Fallback definieren, dann das präzise Muster für Browser, die :focus-visible unterstützen.
/* Fallback: alle Browser */
:focus {
outline: 3px solid var(--focus-ring-outer);
outline-offset: 3px;
}
/* Präzises Muster für moderne Browser */
@supports selector(:focus-visible) {
:focus:not(:focus-visible) {
outline: none;
}
:focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-inner);
outline-offset: var(--focus-ring-offset);
box-shadow: 0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-ring-outer);
}
}
| Was | Warum |
|---|---|
| :focus:not(:focus-visible) statt :focus | Fokus-Ring nur gezielt entfernen |
| Nie outline: none ohne Ersatz | WCAG 2.4.7 Focus Visible absichern |
| 3:1 Kontrast zur Umgebung | WCAG 1.4.11 Non-text Contrast |
| Mind. 3px Ring plus Offset | robuste Praxis, orientiert an WCAG 2.4.13 AAA |
| Zwei-Farben-Ring prüfen | funktioniert auf wechselnden Hintergründen |
| forced-colors-Fallback mit Highlight | Fokus in Kontrastmodi sichtbar halten |
| box-shadow nie allein | wird in Forced Colors Mode entfernt |
| Sticky Header / Overlays testen | WCAG 2.4.11 Focus Not Obscured beachten |
| :has(:focus-visible) für Container | modernes focus-visible-within |