Zum Inhalt springen
Interaktion

Dropdowns ohne JavaScript mit popover und Anchor Positioning

Wie das popover-Attribut Ebene, Klick daneben und Escape übernimmt, wie Anchor Positioning das Panel an seinen Knopf hängt, warum der Übergang zwei Zusätze braucht und was dabei trotzdem deine Aufgabe bleibt.

11 Min. LesezeitStand: 21. September 2026
PopoverCSSInteraktionBarrierefreiheit
Knopf und Panel01
Serie 04
Licht, das den ganzen Tag bleibt
Sechs Leuchten aus einer Werkstatt in Utrecht.
Serie ansehenShowroom

An einem Dropdown lernt man, wie viel Arbeit in etwas Kleinem steckt. Ein Panel, das aufgeht, an seinem Knopf hängt, bei einem Klick daneben verschwindet, auf Escape schließt und nicht vom nächsten overflow: hidden abgeschnitten wird: Das waren jahrelang ein paar hundert Zeilen oder eine Bibliothek. Inzwischen liegen beide Hälften des Problems in der Plattform, das Verhalten seit 2024 im popover-Attribut, die Lage seit Anfang 2026 im Anchor Positioning. Dieser Beitrag zeigt, was dabei wirklich wegfällt, was bleibt und wie das Ganze in einem Tailwind-Projekt aussieht.

Das Wichtigste in Kürze
  • popover übernimmt das Verhalten: oberste Ebene, Klick daneben schließt, Escape schließt und gibt den Fokus zurück, und es ist immer nur eines offen. Kein Zustand im Code, kein Listener am document.
  • Anchor Positioning übernimmt die Lage: anchor-name am Knopf, position-anchor am Panel, position-area setzt es in eines von neun Feldern rund um den Knopf. Am Bildschirmrand klappt position-try-fallbacks es auf die andere Seite.
  • Der Übergang braucht zwei Zusätze, weil display und overlay keine Zwischenwerte kennen: allow-discrete in der Transition und ein @starting-style für den Anfangswert. Ohne beides erscheint das Panel hart.
  • Ein Popover ist nie modal. Für einen Ablauf, der die Seite anhält, bleibt dialog mit showModal, und ein Menü mit Pfeiltasten baust du weiterhin selbst.
01

Warum kostet ein Dropdown so viel Code?

Die Anforderungen an ein Dropdown sind kurz, und sie sind in jedem Projekt dieselben. Das Panel soll über allem liegen, an seinem Knopf hängen, bei einem Klick daneben verschwinden, auf Escape schließen und den Fokus dorthin zurückgeben, wo er hergekommen ist. Fünf Sätze, aus denen regelmäßig mehrere hundert Zeilen werden.

Der Grund ist, dass jeder dieser Sätze gegen eine andere Eigenschaft des Dokuments arbeitet. Über allem liegen heißt, gegen jeden z-index anzukommen, der weiter oben im Baum vergeben wurde. Am Knopf hängen heißt, die Position bei jedem Scrollen und jeder Größenänderung neu zu rechnen. Klick daneben heißt, einen Listener am document zu setzen und ihn zuverlässig wieder abzuräumen. Und ein overflow: hidden an irgendeinem Vorfahren schneidet das fertige Panel trotzdem ab.

Deshalb greifen die meisten zu einer Bibliothek, und das war lange die richtige Entscheidung. Nur hat sich die Grundlage verschoben. Die beiden Hälften des Problems liegen jetzt getrennt voneinander in der Plattform: das Verhalten im popover-Attribut, die Lage im Anchor Positioning. Man kann die eine ohne die andere benutzen, und in der Praxis führt genau diese Trennung dazu, dass der Umstieg in zwei kleinen Schritten geht statt in einem großen.

Ein Dropdown ist kein Layoutproblem. Es ist ein Ebenenproblem mit einem Layoutanteil.

02

Was bringt das popover-Attribut mit?

Ein Element mit dem Attribut popover ist zunächst unsichtbar, das Browser-Stylesheet setzt display: none. Ein Knopf mit popovertarget und der passenden id öffnet es. Mehr Verdrahtung gibt es nicht, und es gibt keinen Zustand, den du irgendwo führen müsstest.

Beim Öffnen wandert das Element in die oberste Ebene, den sogenannten Top Layer. Das ist keine besonders hohe z-index-Stufe, sondern eine eigene Fläche über dem gesamten Dokument. Dort schneidet kein overflow: hidden etwas ab, und kein Stapelkontext weiter oben kommt dagegen an. Es ist genau die Eigenschaft, die man sonst mit einem Portal an den body nachbaut, nur ohne den Umzug im Baum.

Dazu kommt das Verhalten. Ein Klick daneben schließt, Escape schließt und gibt den Fokus an den Knopf zurück, die Tabulatortaste führt als Nächstes in das Panel hinein, und ein zweites Popover schließt das erste. Diese Regeln gelten für popover="auto", die Voreinstellung. popover="manual" lässt sie weg und wartet auf deinen Befehl, popover="hint" ist für kurze Einblendungen gedacht, die ein offenes Menü daneben nicht zuklappen sollen.

<button popovertarget="konto">Konto</button>   <div id="konto" popover>    <a href="/konto">Profil</a>    <a href="/abmelden">Abmelden</a>  </div> <!-- und von innen wieder zu -->  <button popovertarget="konto" popovertargetaction="hide">    Schliessen  </button>
Knopf, Panel, fertig. Kein Zustand, kein Listener.
Die Voreinstellung steht in der BildmitteEin frisches Popover erscheint mittig, mit Rahmen und etwas Innenabstand. Das kommt aus dem Browser-Stylesheet, das position: fixed, inset: 0 und margin: auto setzt. Wer das Panel später an seinen Knopf hängen will, setzt deshalb zuerst inset: auto und margin: 0 zurück. Ohne diese beiden Zeilen wundert man sich lange, warum die Angaben zur Lage nichts bewirken.
03

Wie kommt das Panel an seinen Knopf?

Anchor Positioning verbindet zwei Elemente. Der Knopf bekommt mit anchor-name einen Namen, das Panel verweist mit position-anchor darauf. Beide Namen beginnen mit zwei Bindestrichen, wie eine Custom Property. Sie müssen nicht im selben Elternelement stehen, und das ist der ganze Punkt: Der Anker sitzt im Dokument, das Panel in der obersten Ebene, und die Verbindung hält trotzdem.

Die Lage beschreibst du danach nicht in Pixeln, sondern in Feldern. position-area legt ein Raster aus drei mal drei Feldern um den Anker, der Anker selbst ist die Mitte. bottom center setzt das Panel mittig darunter, bottom span-inline-start hängt es unten an der Startkante auf und lässt es nach innen laufen. Die logischen Angaben drehen sich in Sprachen mit anderer Leserichtung von selbst mit, die physischen bleiben, wo sie sind.

Der eigentliche Gewinn steckt in der Ausweichstelle. position-try-fallbacks nimmt eine Liste, die der Browser der Reihe nach durchgeht, sobald die erste Wahl über den sichtbaren Rand ragen würde. flip-block spiegelt nach oben, flip-inline zur anderen Seite, und ein eigener @position-try-Block beschreibt eine beliebige weitere Stelle mit eigenen Angaben. Das ist genau die Rechnung, die eine Bibliothek sonst bei jedem Scrollen im Hauptstrang ausführt.

.ausloeser {  anchor-name: --konto;} #konto {  position-anchor: --konto;  position-area: bottom span-inline-start;  inset: auto;  margin: 0;  margin-block-start: 0.5rem;  position-try-fallbacks: flip-block, flip-inline;  min-width: anchor-size(width);}
Anker, Lage, Breite und zwei Ausweichstellen.
Von Hand gerechnet
  • Die Lage bei jedem scroll- und resize-Ereignis neu bestimmen. Das läuft im Hauptstrang und ruckelt genau dann, wenn ohnehin viel los ist.
  • Das Panel per Portal an den body hängen, damit kein overflow: hidden es abschneidet. Damit ist es aus dem Dokument heraus, und die Reihenfolge für Tastatur und Vorlesen stimmt nicht mehr mit dem überein, was man sieht.
  • Die Breite des Panels fest eintragen, damit sie zum Knopf passt. Beim ersten längeren Beschriftungstext passt sie nicht mehr.
  • Einen z-index vergeben, der hoch genug ist. Er ist nie hoch genug, er ist nur höher als das, was gerade getestet wurde.
Vom Browser gerechnet
  • position-area beschreibt die gewünschte Stelle einmal, und der Browser hält sie beim Scrollen von selbst.
  • Die oberste Ebene ersetzt das Portal. Das Element bleibt im Dokument, wo es hingehört, und liegt trotzdem über allem.
  • min-width: anchor-size(width) koppelt die Breite an den Auslöser, egal wie lang die Beschriftung wird.
  • position-try-fallbacks nennt die erlaubten Ausweichstellen. Welche davon nötig ist, entscheidet der Browser im Moment des Öffnens.
Ein Anker braucht beide Seitenanchor-name und position-anchor gehören zusammen, und beide müssen gesetzt sein. Fehlt eine Seite oder ist ein Name verschrieben, fällt das Panel wortlos auf seine Voreinstellung zurück und steht wieder mittig im Bild. Eine Warnung in der Konsole gibt es dafür nicht. Wer ein Popover unvermittelt in der Bildmitte findet, prüft deshalb zuerst die Schreibweise der beiden Namen.
04

Warum blendet sich das Panel nicht weich ein?

Beim Öffnen wechselt ein Popover von display: none zu display: block und landet zugleich in der obersten Ebene. Beide Eigenschaften, display und overlay, kennen keine Zwischenwerte, sie springen. Eine Transition auf opacity läuft deshalb ins Leere: Im Moment, in dem sie starten müsste, ist das Element noch gar nicht dargestellt.

Dafür gibt es zwei Angaben, die genau zusammengehören. allow-discrete hinter der Dauer sagt dem Browser, dass er diese Sprünge ans Ende der Transition legen soll statt an den Anfang. Und ein @starting-style-Block beschreibt den Wert, mit dem ein Element in seine allererste Darstellung geht. Ohne den zweiten Teil gibt es keinen Anfangswert, von dem aus animiert werden könnte, und das Panel erscheint hart, während das Schließen bereits weich läuft.

Beim Schließen läuft dieselbe Mechanik rückwärts, und dort reicht allow-discrete allein. Wichtig ist nur, dass display und overlay beide in der Liste stehen. Vergisst man overlay, verlässt das Element die oberste Ebene, bevor die Transition fertig ist, und die letzten Bilder sieht man hinter dem übrigen Inhalt durchscheinen.

[popover] {  opacity: 0;  translate: 0 -0.35rem;  transition:    opacity 180ms ease,    translate 180ms ease,    display 180ms allow-discrete,    overlay 180ms allow-discrete;} [popover]:popover-open {  opacity: 1;  translate: 0 0;}   @starting-style {    [popover]:popover-open { opacity: 0; translate: 0 -0.35rem; }  }
Vier Zeilen, die aus einem Sprung eine Bewegung machen.
Unter 200 Millisekunden bleibenEin Panel, das an einem Knopf hängt, legt eine sehr kurze Strecke zurück. Alles darüber fühlt sich an, als zögere die Oberfläche, weil Klick und Ergebnis auseinanderfallen. Ein leichtes Ease-out und eine Verschiebung von wenigen Pixeln aus der Richtung, aus der das Panel kommt, reichen völlig. Wer prefers-reduced-motion beachtet, lässt dort die Verschiebung weg und behält die Einblendung.

Zwei Eigenschaften springen, drei können fließen. Nur zusammen ergeben sie eine Bewegung.

Drei Kurven decken den Alltag ab.

Eine Seite mit drei Kurven wirkt geordnet, eine mit zwölf wirkt zufällig. Diese drei reichen fast immer.

Weich0.22, 1, 0.36, 1Alles, was hereinkommt
Symmetrisch0.4, 0, 0.2, 1Auf und zu, hin und zurück
Ruhig0.32, 0.72, 0, 1Lange Wege über die Seite
05

Was erledigt popover nicht?

Der Gewinn ist echt, und er ist begrenzt. Das popover-Attribut beschreibt eine Ebene und ein Öffnungsverhalten, keine fertige Komponente. Alles darüber hinaus bleibt Arbeit, und es ist genau die Arbeit, die man beim Umstieg gern übersieht, weil das Ergebnis auf den ersten Blick schon richtig aussieht.

  1. Ein Popover ist nie modal. Der Rest der Seite bleibt bedienbar, und der Fokus wird nicht eingesperrt. Für einen Ablauf, der die Seite anhalten soll, etwa eine Rückfrage vor dem Löschen, bleibt dialog mit showModal das richtige Element.
  2. Ein Menü ist mehr als eine Liste. Wer echte Menüsemantik braucht, also die Rolle menu mit Pfeiltasten und wanderndem Tabindex, baut die weiterhin selbst. Für eine Navigation aus Links ist sie oft gar nicht gewollt: Eine Liste aus a-Elementen im Popover ist mit der Tabulatortaste bedienbar und kommt ohne Menürolle aus.
  3. Ein Tooltip braucht eine Verbindung. Damit ein Hinweis zum beschriebenen Element vorgelesen wird, muss er über aria-describedby daran hängen. Die Ebene allein stellt diese Verbindung nicht her, sie legt den Text nur obendrauf.
  4. Die Reihenfolge im Dokument zählt weiter. Die oberste Ebene ändert die Darstellung, nicht den Baum. Steht das Panel im Quelltext weit weg von seinem Knopf, springt der Fokus sichtbar durch die Seite. Am ruhigsten bleibt es, wenn das Panel direkt hinter seinem Auslöser steht.

Was die Browser dagegen von selbst übernehmen, ist die Verbindung zwischen Knopf und Panel. Ein Knopf mit popovertarget meldet Hilfsmitteln, dass er etwas auf- und zuklappt und welches Element dazugehört. Ein eigenes aria-expanded, das man beim nächsten Umbau vergisst und das danach das Gegenteil des Zustands behauptet, braucht es dafür nicht.

NameE-MailAbsenden
Tabbewegt den Ring, focus-visible macht ihn sichtbar.
Escape schließt das Panel und gibt den Fokus an den Auslöser zurück. Diesen Weg zurück baut man sonst von Hand.

popover regelt die Ebene. Die Semantik der Komponente regelst weiterhin du.

06

Wie sieht das in einem Tailwind-Projekt aus?

Tailwind v4 kennt die neuen Zustände bereits. Die open-Variante trifft neben einem offenen details-Element auch :popover-open, die starting-Variante schreibt in einen @starting-style-Block, und transition-discrete setzt transition-behavior: allow-discrete. Damit passt der ganze Übergang aus dem vorigen Abschnitt in eine Klassenliste, ohne dass eine eigene CSS-Datei dazukommt.

Für die beiden Anker-Eigenschaften gibt es noch keine eigenen Utilities, und das ist kein Problem: Sie kommen in eckigen Klammern als beliebige Eigenschaft dazu. Sobald derselbe Ankername an der dritten Stelle auftaucht, ist allerdings der Moment gekommen, ihn in eine kleine Komponentenklasse zu legen, statt die Klammer weiterzukopieren.

In React fällt der Zustand weg. Kein useState für offen und geschlossen, kein useEffect für den Klick daneben, kein Ref auf das Panel. Übrig bleibt Markup. Nur wenn du von außen öffnen willst, etwa nach einer erfolgreichen Antwort vom Server, greifst du per Ref zu showPopover, hidePopover oder togglePopover.

<button  popoverTarget="konto"  className="[anchor-name:--konto] rounded-lg px-4 py-2"   Konto</button> <div  id="konto"  popover=""  className="    [position-anchor:--konto] [position-area:bottom_span-inline-start]    inset-auto m-0 mt-2 w-56 rounded-xl bg-white p-2 shadow-lg    transition-all transition-discrete duration-200    opacity-0 open:opacity-100 starting:open:opacity-0  " 
Derselbe Knopf und dasselbe Panel, diesmal als Klassenliste.
@supports trennt die beiden Hälften sauberDas Verhalten ist deutlich älter als die Lage, und beide lassen sich getrennt absichern. Eine Abfrage auf @supports (anchor-name: --a) liefert den modernen Zweig, der Block davor bleibt bei der alten absoluten Positionierung mit ihren bekannten Grenzen. Das Popover selbst, also Ebene, Klick daneben und Escape, funktioniert in beiden Zweigen gleich. Damit ist der schlechteste Fall ein Panel an einer weniger eleganten Stelle, nicht eine Komponente, die nicht aufgeht.
07

Wo lohnt sich der Umstieg?

Nicht jede Komponente, die aussieht wie ein Dropdown, wird durch den Umstieg einfacher. Der Gewinn ist dort am größten, wo das Panel kurz lebt, wenig kann und bisher trotzdem eine Bibliothek mitgebracht hat. Vier Gruppen lassen sich klar unterscheiden.

  1. Lohnt sich sofort. Kontomenüs, Filterpanels, Teilen-Menüs, Hinweise an einem Formularfeld, kurze Auswahllisten. Alles, was aufgeht, eine Sache tut und wieder zugeht.
  2. Lohnt sich mit etwas Arbeit. Ein Navigationsmenü mit Untermenüs. Die Ebene, das Schließen und die Ausweichstelle am Rand bekommst du geschenkt, die Bedienung mit Pfeiltasten baust du dazu.
  3. Bleibt bei dialog. Alles, was die Seite anhalten soll: Rückfragen, Formulare über mehrere Schritte, Abläufe, aus denen man nicht versehentlich herausklicken darf.
  4. Bleibt bei einer Bibliothek. Eine Combobox mit Tippen und Filtern, eine Datumsauswahl, ein Multiselect. Dort steckt die Arbeit in Tastatur und Zuständen, nicht in der Ebene, und genau diesen Teil nimmt popover dir nicht ab.

Ein brauchbarer Prüfstein ist die Frage, wie viele Zeilen der JavaScript-Anteil nach dem Umbau noch hat. Bleiben null, ist der Umstieg fertig. Bleiben zwanzig für die Pfeiltasten, ist er es auch. Bleiben zweihundert, hat die Komponente ein anderes Problem, und dieses Problem löst kein Attribut.

Die Ebene ist gelöst. Die Tastatur ist es nicht, und sie war schon immer der schwierigere Teil.

Teilen
Geschrieben von
Amelie RoesmannCreative DirectionLeonie RoesmannDesign Engineering

Wir bauen Websites und Web-Apps bei Systra Studios in Münster und legen die Bausteine daraus hier ab. Was in den Beiträgen steht, kommt aus echten Projekten, nicht aus einem Werbeprospekt.

Systra Studios: Webdesign aus Münster

Du kennst die Kurven.
Die Werte musst du nicht raten.

Eine Sammlung mit Vorschau: Kurve ansehen, laufen lassen, Wert kopieren. Für CSS und für framer-motion derselbe Wert.

  • Jede Kurve läuft zum Anschauen, bevor du sie nimmst.
  • Ein Klick kopiert den cubic-bezier-Wert.
  • Dieselben Werte stecken in allen Komponenten.
Easings ansehen
Weich0.22, 1, 0.36, 1
Symmetrisch0.4, 0, 0.2, 1
Ruhig0.32, 0.72, 0, 1
Ohne Easing0, 0, 1, 1
FAQ

Häufige Fragen

Die beiden Teile haben ein unterschiedliches Alter, und deshalb lautet die Antwort zweimal anders. Das popover-Attribut ist seit April 2024 in allen großen Browsern verfügbar und gilt damit als etabliert. Anchor Positioning ist deutlich jünger: Chromium liefert es seit Mitte 2024 aus, Safari seit Version 26, Firefox kam zuletzt dazu, und position-area gilt seit Anfang 2026 als neu verfügbar. Wer noch ältere Browser bedient, bekommt dort ein Popover, das in der Bildmitte erscheint statt am Knopf. Das ist unschön, aber bedienbar, und ein @supports-Block mit der alten absoluten Positionierung räumt es auf.

Deine Frage war nicht dabei? Wir helfen gern weiter.

Kontakt aufnehmen