CSS · Accessibility · WCAG · Keyboard · UX
CSS Focus Management
:focus-visible, :focus-within und Keyboard-Navigation

Gutes Focus-Management trennt Webentwicklung von zugänglicher Webentwicklung. :focus-visible zeigt den Fokusring nur bei Tastaturnutzung, :focus-within ermöglicht Container-Reaktionen — zusammen mit korrekten outline-Styles nach WCAG bilden sie das Fundament für Keyboard-Navigation ohne Kompromisse beim visuellen Design.

12 Min. Lesezeit :focus-visible · :focus-within · outline · WCAG 2.1 · Focus Trap Alle modernen Browser · WCAG 2.1 AA

1. Das Focus-Dilemma: Sichtbarkeit vs. Ästhetik

Jahrelang stand Webentwickler vor einem scheinbaren Widerspruch: Der Browser-eigene Fokusring — ein blauer oder schwarzer Rahmen um fokussierte Elemente — ist für Tastaturnutzer unverzichtbar, wirkt aber auf vielen Designs visuell störend, besonders wenn Nutzer durch Mausklick auf Buttons oder Links einen blauen Ring erzeugen. Die verbreitete "Lösung" war outline: none in globalen CSS-Resets, was Keyboard-Navigation für Nutzer, die auf visuelle Fokusanzeigen angewiesen sind, faktisch zerstörte.

Das Dilemma ist keines, das man durch Kompromisse lösen muss — es gibt eine saubere technische Lösung. Die CSS-Pseudoklasse :focus-visible aktiviert Focus-Styling selektiv: Bei Tastatureingabe wird der Fokusring angezeigt, bei Mausklick nicht. Das entspricht dem Verhalten, das Nutzer intuitiv erwarten: Wer mit der Maus klickt, braucht keine sichtbare Fokusanzeige; wer mit der Tastatur navigiert, ist auf sie angewiesen. :focus-visible macht genau diese Unterscheidung möglich, ohne JavaScript und ohne heuristische Browsererkennung.

2. :focus vs. :focus-visible — der entscheidende Unterschied

Die Pseudoklasse :focus wird immer dann aktiv, wenn ein Element den Fokus erhält — ob durch Tastatur, Mausklick, Touch oder programmatisches element.focus(). :focus-visible hingegen ist selektiv: Der Browser entscheidet basierend auf einem internen Heuristik-Algorithmus, ob der Fokus "sichtbar gemacht werden soll". Bei Tastaturnavigation ist das immer der Fall. Bei Mausklick auf die meisten interaktiven Elemente nicht — außer bei Elementen, bei denen Nutzer normalerweise Text eingeben (Inputs, Textareas), wo der Fokusring immer gezeigt wird.

Der Unterschied ist praktisch bedeutsam: Mit CSS :focus-visible kann man outline: none für Mausnutzer effektiv setzen, ohne die Tastaturzugänglichkeit zu opfern. Das Muster ist einfach: Erst den Standard-Fokus neutralisieren, dann :focus-visible mit einem schönen, WCAG-konformen Outline-Stil versehen. Browser, die :focus-visible nicht unterstützen, ignorieren die zweite Regel und behalten den Standard-Fokusring — was ein gültiger Progressive-Enhancement-Fallback ist.


/* Step 1: Remove default focus ring (it will be replaced) */
:focus:not(:focus-visible) {
  outline: none;
}

/* Step 2: Style focus-visible with WCAG-compliant indicator */
:focus-visible {
  outline: 3px solid rgb(124 58 237);   /* Violet, meets contrast requirements */
  outline-offset: 3px;
  border-radius: 4px;
}

/* Custom per-component focus styles */
.btn:focus-visible {
  outline: 3px solid rgb(196 181 253);
  outline-offset: 4px;
  box-shadow: 0 0 0 6px rgb(124 58 237 / 0.25);
}

.card:focus-visible {
  outline: 2px solid rgb(124 58 237);
  outline-offset: 2px;
}

/* Dark background: invert focus ring for contrast */
.dark-section :focus-visible {
  outline-color: rgb(196 181 253);  /* Light purple on dark background */
}

Ein wichtiger Hinweis: outline: none global auf alle Elemente zu setzen und nur für :focus-visible zurückzusetzen ist der empfohlene Ansatz. Die Alternative :focus:not(:focus-visible) { outline: none } ist ebenfalls korrekt und expliziter. Beide Ansätze erfordern, dass :focus-visible-Styles für alle interaktiven Elemente definiert sind — eine globale Regel reicht oft aus, aber komponentenspezifische Anpassungen verbessern die visuelle Konsistenz.

3. outline richtig stylen nach WCAG-Standards

WCAG 2.1 (Web Content Accessibility Guidelines) definiert in Erfolgskriterium 2.4.11 (Focus Appearance, Level AA in WCAG 2.2) konkrete Anforderungen für Fokusindikatoren. Der Fokusindikator muss eine minimale Fläche von der Breite des Umrisses mal dem Umfang des Elements aufweisen, und das Kontrastverhältnis des Indikators zur angrenzenden Fläche muss mindestens 3:1 betragen. In der Praxis bedeutet das: Ein outline mit mindestens 2 Pixeln Breite und ausreichendem Kontrast zum Hintergrund des fokussierten Elements sowie zu seiner eigenen nicht-fokussierten Farbe.

Die CSS-Eigenschaft outline-offset ist dabei besonders wichtig. Sie verschiebt den Outline-Ring nach außen und verhindert, dass er mit dem Element-Border kollidiert. Ein outline-offset: 3px schafft optisch ansprechende, klar erkennbare Fokusringe, die sich vom fokussierten Element abheben. Die Kombination aus outline und box-shadow ermöglicht komplexere Fokus-Stile: Der box-shadow kann einen doppelten Ring erzeugen, der auf hellen und dunklen Hintergründen gleichermaßen gut sichtbar ist.


/* WCAG 2.2-compliant focus indicators */

/* Universal focus base — high visibility */
*:focus-visible {
  outline: 3px solid rgb(124 58 237);
  outline-offset: 3px;
  /* Rounded corners follow element's border-radius */
  border-radius: inherit;
}

/* Double-ring pattern: visible on any background */
.interactive:focus-visible {
  outline: 3px solid rgb(124 58 237);
  outline-offset: 2px;
  /* White gap between element and outline */
  box-shadow:
    0 0 0 2px white,
    0 0 0 5px rgb(124 58 237);
}

/* High-contrast mode support */
@media (forced-colors: active) {
  :focus-visible {
    outline: 3px solid ButtonText;
    outline-offset: 3px;
  }
}

/* Reduced-motion: no animated focus transitions */
@media (prefers-reduced-motion: reduce) {
  :focus-visible {
    transition: none;
  }
}

/* Focus indicator for links within text */
a:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
  text-decoration-color: transparent;
}

4. :focus-within für Container-Zustände

Die Pseudoklasse :focus-within wird auf einem Element aktiv, wenn dieses Element selbst oder eines seiner Nachfahren den Fokus hat. Das ermöglicht Styling-Reaktionen auf Containerebene, die ohne JavaScript nicht möglich waren: Ein Formular-Container kann hervorgehoben werden, wenn ein beliebiges Eingabefeld darin aktiv ist. Eine Navigationsgruppe kann sich öffnen, wenn ein Untermenü-Element fokussiert wird. Ein Suchfeld-Wrapper kann seinen Label-Stil ändern, wenn das darin enthaltene Input fokussiert ist.

Typischer Einsatzfall für CSS :focus-within: Ein Formular-Label, das sich beim Fokus des zugehörigen Inputs nach oben bewegt (Floating Label Pattern). Mit :focus-within auf dem Wrapper-Element lässt sich dieser Effekt rein in CSS umsetzen. Weitere Anwendungen: Dropdown-Menüs, die sich bei Fokus öffnen und bei Fokusverlust schließen; Suchleisten, die sich ausweiten; Tooltips, die bei Fokus erscheinen. :focus-within ist das CSS-Äquivalent zu JavaScript's focusin-Event auf Container-Ebene.

5. Focus Trap in Modalen und Dialogen

Ein Focus Trap hält den Tastaturfokus innerhalb eines bestimmten Elements — in der Regel einem Modal oder Dialog — und verhindert, dass der Nutzer mit der Tab-Taste in dahinterliegende Inhalte navigiert. Das ist für Accessibility unverzichtbar: Wenn ein Modal geöffnet ist, muss der Fokus im Modal bleiben, bis der Nutzer es explizit schließt. Andernfalls können Tastaturnutzer versehentlich Elemente hinter dem Modal aktivieren, ohne zu wissen, dass ihr Fokus das Modal verlassen hat.

Das HTML <dialog>-Element implementiert Focus-Trap nativ, wenn es mit showModal() geöffnet wird. Für Custom-Modale ohne das native <dialog>-Element ist JavaScript für den vollständigen Focus Trap nötig. CSS allein kann keinen echten Focus Trap erzeugen — CSS kann aber dazu beitragen, indem es alle fokussierbaren Elemente außerhalb des Modals mit inert oder tabindex="-1" aus dem Tab-Order entfernt. Modernes CSS mit visibility: hidden auf dem Backdrop-Overlay oder pointer-events: none auf dem Hintergrund schränkt Interaktionen ein, aber Tab-Navigation ist davon unabhängig.


/* Focus management for native <dialog> element */
dialog {
  /* Native dialog: focus trap is automatic with showModal() */
  border-radius: 1rem;
  padding: 2rem;
  border: 1px solid rgb(226 232 240);
  box-shadow: 0 20px 60px rgb(0 0 0 / 0.3);
  max-width: min(90vw, 40rem);
  max-height: min(85dvh, 50rem);
  overflow-y: auto;
}

/* Backdrop styling */
dialog::backdrop {
  background: rgb(0 0 0 / 0.6);
  backdrop-filter: blur(4px);
}

/* First focusable element gets focus on open */
dialog h2:first-child {
  /* Not focusable by default — add tabindex="-1" in HTML */
  /* Then use JS: dialog.querySelector('h2').focus() */
}

/* Skip link — visible only on keyboard focus */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 9999;
  padding: 1rem 2rem;
  background: rgb(124 58 237);
  color: white;
  font-weight: bold;
  border-radius: 0 0 0.5rem 0;
}

.skip-link:focus-visible {
  left: 0;
  outline: 3px solid white;
  outline-offset: 2px;
}

Skip-Links sind Anker-Links am Anfang einer Seite, die es Tastaturnutzern ermöglichen, wiederholte Navigationselemente zu überspringen und direkt zum Hauptinhalt zu springen. Sie sind eines der wichtigsten Accessibility-Features für Keyboard-Nutzer und WCAG-Erfolgskriterium 2.4.1 (Bypass Blocks). CSS-seitig werden Skip-Links üblicherweise außerhalb des sichtbaren Bereichs positioniert und nur bei Fokus sichtbar gemacht — mit :focus-visible statt :focus für korrekte moderne Implementierung.

Das Skip-Link-Muster mit CSS :focus-visible: Das Element wird mit position: absolute; left: -9999px oder clip: rect(0,0,0,0) versteckt und kehrt bei :focus-visible an seine sichtbare Position zurück. Wichtig: Das Ziel-Element (z.B. #main-content) braucht entweder ein natives Fokus-Attribut oder tabindex="-1", damit :target-Scrolling und programmatisches Fokus-Management korrekt funktionieren. Skip-Links testen am einfachsten durch Drücken von Tab auf einer frisch geladenen Seite.

7. :has() für fokusabhängige Layouts

Die CSS-Pseudoklasse :has() in Kombination mit :focus-visible oder :focus-within eröffnet mächtige neue Möglichkeiten für fokusabhängige Layouts. Mit :has(:focus-visible) kann ein Container auf den Fokus eines beliebigen Nachfahren reagieren — ähnlich wie :focus-within, aber mit der Flexibilität des :has()-Selektors, der auch benachbarte Elemente ansprechen kann. Das Muster .form-group:has(input:focus-visible) label ändert den Label-Stil wenn das zugehörige Input fokussiert ist, ohne dass das Label ein direktes Elternteil des Inputs ist.

Ein weiteres Anwendungsbeispiel: Eine Navigationsleiste, die sich optisch verändert, wenn ein beliebiger Link in ihr fokussiert wird. Mit nav:has(a:focus-visible) { background: rgb(245 243 255) } bekommt die Nav einen hellvioletten Hintergrund sobald ein Link fokussiert wird — ein subtiler, aber effektiver visueller Hinweis für Tastaturnutzer, in welchem Bereich der Seite sie sich befinden. Diese Art von kontextuellem Focus-Management verbessert die Orientierung ohne aufdringlich zu sein.

8. Focus-Strategien im Vergleich

Verschiedene Ansätze für Focus-Management haben unterschiedliche Vor- und Nachteile in Bezug auf Accessibility, visuelle Qualität und Implementierungsaufwand.

Ansatz Keyboard-Nutzer Maus-Nutzer WCAG-Konformität
:focus-visible (modern) Klar sichtbar Kein Ring AA konform
outline: none (global) Kein Fokus-Indikator Kein Ring WCAG-Verstoß
:focus (alles stylen) Sichtbar Ring bei Klick Konform, aber störend
JS-basiertes Focus-Management Flexibel Kontrollierbar Abhängig von Implementierung
Browser-Standard (kein CSS) Funktional Plattformabhängig Meist konform

Die empfohlene Strategie ist klar: :focus-visible für alle interaktiven Elemente, ergänzt durch sorgfältiges outline-Styling nach WCAG-Vorgaben. :focus-within für Container-Zustände. Das native <dialog>-Element wann immer möglich, da es Focus-Trap nativ implementiert. JavaScript nur dort einsetzen, wo CSS keine ausreichende Lösung bietet.

9. Testing und Accessibility-Audit

Focus-Management lässt sich mit einfachen Mitteln testen: Tab-Taste auf der Seite drücken und prüfen, ob alle interaktiven Elemente einen sichtbaren Fokusindikator haben, die Tab-Reihenfolge logisch ist und keine "Fokus-Fallen" außerhalb von Modalen existieren. Browser-DevTools zeigen unter Accessibility den berechneten Fokus-Zustand und ARIA-Attribute. Das axe-DevTools-Plugin für Chrome gibt automatisierte WCAG-Verstöße für Focus-Management aus.

Spezifische Tests für :focus-visible: Jedes interaktive Element per Mausklick fokussieren und prüfen, dass kein Fokusring erscheint. Dann mit Tab navigieren und prüfen, dass der Ring bei jedem Element sichtbar ist und ausreichend kontrast- und größenstark ist. Für Modale: Modal öffnen, Tab drücken und prüfen, dass der Fokus im Modal bleibt und nicht in den Hintergrund-Content wechselt. Escape drücken und prüfen, dass der Fokus zum auslösenden Element zurückkehrt.

Mironsoft

Accessibility-Audit, WCAG-Konformität und zugängliches Frontend-Design

WCAG-konforme Keyboard-Navigation für Ihre Webseite?

Wir führen vollständige Accessibility-Audits durch, identifizieren Focus-Management-Probleme und implementieren :focus-visible, Skip-Links und korrekte Focus-Traps nach WCAG 2.2 AA-Standard.

Accessibility-Audit

Vollständige WCAG 2.2 Prüfung mit Fokus auf Keyboard-Navigation und Focus Management

CSS-Implementierung

:focus-visible, :focus-within und outline-Styles nach WCAG-Standards einrichten

Modal-Accessibility

Focus Trap, ARIA-Attribute und Keyboard-Handler für zugängliche Dialoge

10. Zusammenfassung

Korrektes CSS Focus Management ist kein optionales Feature, sondern Grundlage für barrierefreie Webseiten. Die wichtigsten Bausteine: :focus-visible zeigt den Fokusring selektiv nur bei Tastaturnavigation, nicht bei Mausklick. outline mit mindestens 3 Pixeln Breite und ausreichendem Kontrast entspricht WCAG 2.2 AA. :focus-within ermöglicht Container-Reaktionen auf Fokus-Events ohne JavaScript. Das native <dialog>-Element bietet nativen Focus-Trap für Modale. Skip-Links mit :focus-visible verbessern die Keyboard-Navigation erheblich.

Die Zusammenführung dieser Bausteine ergibt ein robustes Focus-System: Nutzern, die per Maus navigieren, begegnet kein störender Fokusring. Tastaturnutzer haben klare, konsistente Fokusindikatoren auf allen interaktiven Elementen. Screenreader-Nutzer erhalten korrekte Fokus-Reihenfolge und Focus-Traps in Modalen. :has() mit :focus-visible ermöglicht darüber hinaus kontextuelles Styling, das die Orientierung verbessert. Alle diese Techniken funktionieren ohne JavaScript und sind in modernen Browsern vollständig unterstützt.

CSS Focus Management — Das Wichtigste auf einen Blick

:focus-visible

Fokusring nur bei Tastaturnavigation. :focus:not(:focus-visible) { outline: none } für Mausnutzer, dann :focus-visible mit schönem Outline-Stil.

WCAG-konformes Outline

Mindestens 3px Breite, 3:1 Kontrafstverhältnis. outline-offset für optischen Abstand. Double-Ring mit box-shadow für universelle Sichtbarkeit.

:focus-within

Container-Reaktion auf Fokus eines Nachfahren. Floating Labels, Dropdown-Trigger, Suchfeld-Expansion — rein in CSS.

Focus Trap

Natives <dialog> mit showModal() für automatischen Focus Trap. Custom-Modale brauchen JavaScript für vollständige Implementierung.

11. FAQ: CSS Focus Management und Keyboard-Navigation

1Unterschied :focus vs. :focus-visible?
:focus bei jeder Fokusart. :focus-visible nur wenn Browser sichtbaren Indikator als angemessen erachtet — typischerweise bei Tastatur.
2Darf ich outline: none global setzen?
Nein — WCAG-Verstoß. Korrekt: :focus:not(:focus-visible) { outline: none } und :focus-visible mit WCAG-konformem Stil.
3WCAG-Anforderungen an Fokusindikatoren?
WCAG 2.2 AA: Ausreichende Fläche und 3:1 Kontrast. Praxis: outline 3px, ausreichende Farbe, outline-offset für optischen Abstand.
4Wie funktioniert :focus-within?
Aktiv wenn Element selbst oder Nachfahre fokussiert ist. Container-Styling ohne JS — Floating Labels, Dropdowns, Suchfelder.
5Was ist ein Focus Trap?
Hält Fokus innerhalb eines Bereichs — unverzichtbar in Modalen. <dialog> mit showModal() implementiert es automatisch.
6Skip-Links korrekt gestalten?
Versteckt mit left: -9999px, sichtbar bei :focus-visible. Ziel-Element braucht tabindex="-1".
7:has() mit :focus-visible kombinieren?
container:has(input:focus-visible) label — Label-Stil ändert sich bei Input-Fokus. Kein JS für fokusabhängiges Layout nötig.
8Browser-Support :focus-visible?
Chrome 86+, Firefox 85+, Safari 15.4+, Edge 86+. :focus:not(:focus-visible) degradiert gracefully in älteren Browsern.
9Keyboard-Navigation effizient testen?
Tab durch alle interaktiven Elemente. Shift+Tab rückwärts. Enter/Space für Buttons. Escape für Modale. axe-DevTools für automatisierte Prüfungen.
10High-Contrast-Mode unterstützen?
@media (forced-colors: active) für Windows High Contrast Mode. outline: 3px solid ButtonText für maximale Kompatibilität.