Das Muster kennt jeder. Man klickt auf Teilen, der Rest der Seite tritt zurück, ein kleines Fenster rückt die Optionen zum Teilen in den Vordergrund. YouTube macht es so, unzählige andere auch. Und Bootstrap bringt so ein Modal fertig mit – ein paar Zeilen Markup, eine Zeile JavaScript, steht.

Fast. Denn in der Bootstrap-Dokumentation steht ein Satz, den man leicht überliest: Die Überschrift des Modals sollte idealerweise eine <h1> sein. Klingt nach einer Zeile Arbeit. Es war der Anfang einer ganzen Kette von Problemen.

Warum ein Modal in diesem Fall das richtige Werkzeug ist, welche drei Konsequenzen aus einem einzigen Prinzip folgen und wie ich sie in meinem Theme gelöst habe, kannst du in diesem Beitrag lesen.

Hinweis: Die Code-Ausschnitte in diesem Beitrag sind zur Erklärung gekürzt – sie zeigen die Kernidee, nicht den vollständigen Code aus meinem Theme.

Warum überhaupt ein Modal?

Weil das «Teilen» eine abgeschlossene Nebenhandlung ist – man will sie anbieten, ohne den Leser von der Seite wegzuführen. Genau dafür ist ein Modal gemacht.

Zwei Dinge sprechen dafür. Erstens der Fokus: Solange der Teilen-Dialog offen ist, tritt der Rest der Seite zurück – abgedunkelt hinter einem Backdrop, für Maus, Tastatur und Screenreader vorübergehend inaktiv. Die Aufmerksamkeit liegt auf einer Sache, nicht auf vielen. Zweitens der Inhalt: Was im Modal steht, ist ein in sich geschlossener Kontext. Er gehört nicht in den Lesefluss des Beitrags.

Und genau der zweite Punkt ist der Schlüssel zu allem, was folgt. Ein Modal ist kein Abschnitt der Seite, der zufällig gerade sichtbar ist. Er ist ein eigenes Dokument mit eigenem Kontext – die Bootstrap-Dokumentation sagt es, und daran hängt mehr, als man auf den ersten Blick denkt. Wenn man diesen Satz ernst nimmt, muss man ihn dreimal einlösen: bei der Überschrift, beim Fokus und dort, wo Modal und Tooltip aufeinandertreffen.

Die Überschrift, die keine sein darf

Ein eigenes Dokument, wie ein Modal, braucht eine eigene Top-Level-Überschrift – eine <h1>.

Doch was ist mit «eigenes Dokument» ganz genau gemeint? Es geht nicht um eine zweite Datei oder eine zweite Seite. Technisch bleibt ein Modal im Rahmen einer einzigen Seite – innerhalb eines einzigen <html>-Gerüsts, das der Browser lädt und das ein Validator als ein zusammenhängendes HTML-Dokument prüft. Gemeint ist die semantische Ebene: Für den Nutzer, der den Teilen-Dialog (Modal) öffnet, beginnt dort inhaltlich ein neuer, in sich geschlossener Kontext – und dieser Kontext verdient dieselbe Behandlung wie ein eigenständiges Dokument, inklusive eigener Top-Level-Überschrift. Er besteht sozusagen parallel zur eigentlichen Seite (ein Dokument im Dokument), obwohl er Teil des <html>-Gerüsts ist.

Deshalb empfiehlt Bootstrap, die .modal-title als <h1> auszuzeichnen. Normalerweise darf mitten auf einer Seite nicht einfach eine zweite <h1>-Überschrift auftauchen, weil sie mit der eigentlichen, die Seite einleitenden <h1> konkurriert. Optisch muss das nichts heissen – über eine CSS-Utility-Klasse wie .h4 oder eigene CSS-Definitionen kann man den Stil kontrollieren. Strukturell zeigt sich jetzt aber ein Haken, wenn man das «einfach so» macht.

Der W3C-Validator behandelt jede <h1> als oberste bzw. Top-Level-Überschrift ebendieses einen Dokuments – und meine Seite hat bereits eine, den Beitragstitel. Zwei gleichwertige <h1> innerhalb desselben technischen Dokuments sind aus seiner Sicht ein Fehler, ganz gleich, ob die zweite semantisch in einem eigenen Kontext steht. Screenreader und andere Tools sehen es ähnlich: Sie stufen jede <h1> als oberste Ebene ein, egal wo sie steht.

Das Dilemma: Der Modal braucht semantisch seine eigene <h1>, die Seite lässt eigentlich keine zweite zu. Beide zugleich zu verwenden – ohne Zusatzattribute – ist heikel.

Es gibt einen sauberen Weg, diesen Umstand zu lösen – nur kennt ihn aktuell noch kaum ein Browser. Das Attribut headingoffset erlaubt einem Container, die Überschriften darin um eine Stufe herunterzuschieben: Der Autor schreibt <h1>, der Container macht daraus in der Accessibility-Ebene eine Überschrift zweiter Ordnung. Genau das, was ein Modal braucht. headingoffset ist inzwischen Teil der HTML-Spezifikation und der W3C-Validator akzeptiert es – umgesetzt hat es bis Mitte 2026 aber praktisch nur Firefox Nightly, hinter einem Flag. In der Breite ist es noch nicht angekommen; dasselbe gilt für die verwandte CSS-Pseudoklasse :heading().

Also nehme ich, was heute schon funktioniert: aria-level="2". Damit meldet die <h1> an assistive Technik, dass sie in der zweiten Ebene der Überschriften ist, ohne dass sie in einem <h2>-Tag sein muss. Und zwar genau so, wie es das Spec-Beispiel zeigt: Dort steht aria-level="2" direkt neben dem headingoffset-Mechanismus als gleichwertiger Weg.

<h1 class="h4 modal-title" aria-level="2">
    Beitrag teilen
</h1>

Ganz ohne Reibung ist die Lösung aber nicht. Bing zum Beispiel meldet in seinen Webmaster Tools bei jedem Beitrag mit Teilen-Modal einen Hinweis: «Mehr als ein h1-Tag». Das Tool zählt schlicht die <h1> im Quelltext – Beitragstitel und Modal-Titel – und wertet das als zu viele, ohne das aria-level zu berücksichtigen, das die Ebene ja bereits korrigiert. Ein Hinweis, kein Fehler – und einer, der mehr über das Tool aussagt als über das Markup. Der Gegensatz ist bezeichnend: Der W3C-Validator akzeptiert die Konstruktion, ein SEO-Tool bemängelt es.

Ich nehme das bewusst in Kauf. Die technisch sauberste Lösung ist noch nicht in den Browsern angekommen. Bis sie es ist, ist aria-level der ehrlichste und beste Weg – korrekt für assistive Technik heute, auch wenn ein Prüf-Tool eines grossen Software- und Suchmaschinenanbieters die Absicht nicht mitliest (in meinem Beitrag über «Weniger Code, mehr Barrierefreiheit» gibt’s dazu einen Abschnitt: Warum Barrierefreiheit gerade jetzt relevanter wird).

Wohin mit dem Fokus?

Der Fokus muss in den Modal hinein, dort gehalten werden und beim Schliessen wieder zurück zum Auslöser gelangen – sonst verliert man bei der Tastaturnavigation den Faden.

Wenn der Modal öffnet, setze ich den Fokus aktiv auf den Schliessen-Button. Denn auch hier sagt die Bootstrap-Dokumentation – beiläufig, in einem kleinen Nebensatz am Anfang –, dass man sich selbst darum kümmern soll, wohin der Fokus gesetzt wird. Gerade wenn der Modal kein Eingabefeld enthält, ist das wichtig. Vom gesetzten Fokus aus (hier auf dem Schliessen-Button, dem einzigen logischen Ansatzpunkt) kann man jetzt per Tastatur durch die Optionen im Dialog wandern, nicht durch die Seite dahinter. Bootstrap hält den Fokus im Modal – das muss ich nicht selbst programmieren. Was man programmieren muss, ist der saubere Ausgang.

Denn beim Schliessen setzt Bootstrap aria-hidden auf den Modal. Liegt in diesem Moment der Fokus noch auf einem Element darin, entsteht ein Widerspruch: Ein fokussiertes Element darf nicht in einem aria-hidden-Bereich stehen. Chrome quittiert das mit einer Warnung in der Konsole – «Blocked aria-hidden on an element because its descendant retained focus» –, und für Screenreader ist der Zustand schlicht ungültig. Die Lösung ist, den Fokus zu entfernen, bevor Bootstrap den Bereich versteckt.

// Vor dem Schliessen den Fokus aus dem Modal nehmen,
// bevor Bootstrap aria-hidden setzt.
modal.addEventListener( 'hide.bs.modal', () => {
    if ( modal.contains( document.activeElement ) ) {
        document.activeElement.blur();
    }
} );

Dasselbe gilt für die Escape-Taste, die einen eigenen Weg zum Schliessen nimmt – auch hier nehme ich den Fokus vorher weg. Und wenn der Modal zu ist, muss der Fokus zurück an die Stelle wandern, von der er kam: den Button, der den Modal geöffnet hat, der ursprüngliche Auslöser. Sonst springt alles zum Seitenanfang und der Leser muss sich neu orientieren.

// Fokus zurück zum Button, der den Modal geöffnet hat.
sharedModal.addEventListener( 'hidden.bs.modal', () => {
    lastShareBtn.focus();
} );

Nichts von all dem bekommt man bei der Bedienung mit der Maus mit. Bedient man die Seite mit der Tastatur, macht es den Unterschied zwischen «ich weiss jederzeit, wo ich bin» und «wo bin ich jetzt gelandet».

Wenn Tooltip und Modal sich in die Quere kommen

Sobald sich zwei Bootstrap-Komponenten denselben Raum teilen, muss man ihren Lebenszyklus von Hand koordinieren. Genau das muss ich hier machen: Die Teilen-Symbole im Modal tragen Tooltips und der Button, der den Modal öffnet, hat selbst einen.

Kurz erklärt: «Warum eigentlich Tooltips?» Weil sie Symbole textlich erklären können, deren Bedeutung nicht unbedingt intuitiv verstanden wird. Nebenbei kann man damit auch ein wenig visuellen Kontext liefern.

Das erste Problem ist die Höhe. Ein Tooltip, der zu einem Symbol im Modal gehört, wird standardmässig weiter oben im Dokument eingehängt – und liegt damit hinter dem Backdrop, also unsichtbar. Die Lösung ist, ihn im Modal selbst zu verankern. Dann teilt er dessen Stapelkontext und liegt darüber, wo er hingehört (im Dokument des Dokuments).

// Tooltip im Modal verankern, damit er über dem Backdrop liegt.
new bootstrap.Tooltip( link, { container: modal } );

Das zweite Problem ist zeitlicher Natur – und kniffliger. Der Button, der den Modal auslöst, hat einen Tooltip. Klickt man ihn, öffnet der Modal, aber der Tooltip würde noch einen Moment sichtbar bleiben und über dem Backdrop stehen. Also blende ich ihn beim Öffnen nicht nur aus, ich entferne seine Instanz ganz. Und beim Schliessen vom Modal kommt der eigentliche Kniff: Der Fokus kehrt ja zum Button zurück – und ein Tooltip, der auf Fokus reagiert, würde in genau diesem Moment aufpoppen, ohne dass die Maus in der Nähe ist. Deshalb setze ich ihn zuerst deaktiviert wieder auf und schalte ihn erst frei, wenn der Fokus den Button wieder verlässt.

// Nach dem Schliessen den Button-Tooltip neu aufsetzen – zunächst
// deaktiviert, damit er beim zurückkehrenden Fokus nicht sofort aufpoppt.
const tooltip = new bootstrap.Tooltip( lastShareBtn, { animation: false } );
tooltip.disable();
lastShareBtn.focus();
lastShareBtn.addEventListener( 'focusout', () => tooltip.enable(), { once: true } );

Dieselbe Tooltip-Mechanik trägt übrigens noch eine zweite Aufgabe: Beim Kopieren des Beitrag-Links wechselt der Tooltip kurz seinen Text auf eine Bestätigung – «Kopiert!» – und danach zurück. Kein zusätzliches Element, kein eigenes Overlay, nur der Tooltip, der ohnehin schon da ist und jetzt zusätzlich etwas mehr Information zum Vorgang liefert. Das ist die Art Detail, die den kleinen, aber feinen Unterschied in der Bedienung macht.

Ein Satz mit Folgen

Dieser eine Satz aus der Bootstrap-Dokumentation – die Modal-Überschrift solle idealerweise eine <h1> sein – war der Auslöser für alles, was in diesem Beitrag folgte. Ein Modal ist ein eigenes Dokument im Dokument und darf nicht in Konflikt mit der <h1> der Seite geraten.

Bei der Überschrift hiess das: aria-level="2" zu setzen statt einer einfachen zweiten <h1> ohne aria-level – kein Kompromiss, sondern die von der Spezifikation selbst vorgesehene Lösung: gleichwertig zu headingoffset, das die Browser bisher kaum unterstützen. Beim Fokus hiess es: ihn aktiv aus dem Modal zu nehmen, bevor Bootstrap aria-hidden setzt, und ihn beim Schliessen gezielt zum auslösenden Button zurückzuführen. Und bei den Tooltips hiess es: sie im Stapelkontext des Modals zu verankern und ihren Lebenszyklus (deaktivieren beim Öffnen, verzögert wieder aktivieren beim Schliessen) von Hand zu steuern.

Drei Stellen, ein Prinzip. Was nach einer Zeile Arbeit aussah, war am Ende eine Übung darin, eine Empfehlung aus einer Dokumentation nicht nur zu lesen, sondern bis zum Ende durchzudenken und umzusetzen.

FAQ

Weil ein Modal strukturell ein eigenes Dokument ist – ein eigener Kontext mit eigener oberster Überschrift. Bootstrap empfiehlt deshalb, eine <h1> zu verwenden. Damit es sich aber nicht mit der <h1> der Seite beisst, wird die Ebene über aria-level="2" heruntergesetzt auf eine <h2>, ohne ein <h2>-Tag einzusetzen.

headingoffset ist ein HTML-Attribut, das die Überschriften-Ebenen innerhalb eines Containers verschiebt – genau das, was ein Modal braucht. Es ist Teil der HTML-Spezifikation und validiert im W3C-Validator, umgesetzt hat es bis Mitte 2026 aber praktisch nur Firefox Nightly hinter einem Flag. Für den breiten Einsatz reicht der Support noch nicht – genau deshalb ist aria-level="2" heute der von der Spezifikation selbst vorgesehene, gleichwertige Weg.

Damit man bei der Tastaturnavigation den Faden nicht verliert. Ohne Rückgabe landet der Fokus am Seitenanfang. Er muss zurück an die Stelle gehen, von der aus der Modal geöffnet wurde – zum auslösenden Button.

Nein. Bootstrap liefert in meinem Fall die Grundmechanik – Fokus-Falle, Backdrop, Schliessen per Escape. Was umgesetzt werden muss, ist die Überschrift und den Fokus beim Öffnen und Schliessen richtig zu setzen und die Abstimmung mit Tooltips vorzunehmen. All das muss im Theme umgesetzt werden, nicht mit einem Plugin.