mitmario.dev

Ankerpositionierung

CSS Im Browser 4 Min Lesezeit 4 BeispieleLektion 5 von 8

position: absolute sucht sich den nächsten positionierten Vorfahren und richtet sich an dessen Kanten aus, so wie in Lektion 8.3. Das ist eine harte Bedingung. Willst du einen Tooltip an einen Button hängen, musst du eine Hülle um den Button legen. Und soll er an einem Element in einer Tabellenzelle hängen, hast du ein Problem.

Ankerpositionierung dreht das um. Das Bezugselement bekommt einen Namen, und das schwebende Element nennt diesen Namen. Wo die beiden im Baum stehen, ist egal.

Zwei Eigenschaften, ein Name

Der Tooltip hängt am Button
body {
  margin: 0;
  padding: 24px;
  font-family: system-ui, sans-serif;
  line-height: 1.5;
}

.button {
  /* Der Button bekommt einen Namen. */
  anchor-name: --button;
  padding: 8px 16px;
  border: 0;
  border-radius: 8px;
  font: inherit;
  color: #ffffff;
  background-color: #2563eb;
}

.tooltip {
  /* Das schwebende Element nennt den Namen und
     sagt, an welcher Seite es liegen soll. */
  position: absolute;
  position-anchor: --button;
  position-area: block-end span-inline-end;
  margin-top: 8px;
  width: max-content;
  max-width: 220px;
  padding: 8px 16px;
  border-radius: 8px;
  font-size: 14px;
  color: #fafafa;
  background-color: #18181b;
}

anchor-name: --button steht am Bezugselement. Der Name ist ein selbst gewählter Bezeichner mit zwei Bindestrichen davor, genau wie bei Custom Properties. Sonst hat er mit ihnen nichts zu tun.

position-anchor: --button am schwebenden Element nennt den Anker, an dem es hängt. Dazu braucht es weiterhin position: absolute oder fixed, denn schweben muss es trotzdem.

Die beiden Namen stehen nicht im Auskunftsstreifen, dort stehen Layout, Farben und Schrift. Am Tooltip findest du position: absolute und width: 220px, das ist die eine Hälfte. Die andere bekommst du in der Eingabezeile der Console. getComputedStyle(document.querySelector('.button')).anchorName antwortet mit --button und getComputedStyle(document.querySelector('.tooltip')).positionAnchor mit demselben Namen. Ob der Browser überhaupt mitmacht, verrät CSS.supports('anchor-name', '--x'), und das ist @supports als Frage statt als Block.

So nicht, der Name ohne Striche
body {
  margin: 0;
  padding: 24px;
  font-family: system-ui, sans-serif;
  line-height: 1.5;
}

.button {
  /* So nicht: der Name ohne die zwei Striche. Das ist
     kein gültiger Wert, und die Zeile fällt weg. */
  anchor-name: button;
  padding: 8px 16px;
  border: 0;
  border-radius: 8px;
  font: inherit;
  color: #ffffff;
  background-color: #2563eb;
}

.tooltip {
  position: absolute;
  position-anchor: button;
  position-area: block-end span-inline-end;
  margin-top: 8px;
  width: max-content;
  max-width: 220px;
  padding: 8px 16px;
  border-radius: 8px;
  font-size: 14px;
  color: #fafafa;
  background-color: #18181b;
}

Im Beispiel fehlen die beiden Striche an beiden Stellen, anchor-name: button und position-anchor: button. Ohne sie ist ein Name kein gültiger Wert, und der Browser verwirft beide Zeilen. Der Tooltip hängt dann nicht unter dem Button. Er liegt auf seiner Höhe daneben, ungefähr dort, wo er ohne jede Angabe im Textfluss stünde. Firefox schiebt ihn dabei noch ein Stück über die rechte Kante des Buttons. In der Eingabezeile der Console siehst du es auch, denn anchorName antwortet am Button mit none.

Mit position-area legst du dann fest, wo genau. Dahinter steckt ein Raster aus neun Feldern rund um den Anker. block-start ist oben, block-end unten, inline-start und inline-end sind links und rechts, und span-inline-end heißt “und von dort nach rechts ausdehnen”. Brauchst du es genauer, rechnest du mit anchor() in top, left und den anderen Verschiebungen einzelne Werte aus.

Derselbe Tooltip mit position absolute
body {
  margin: 0;
  padding: 24px;
  font-family: system-ui, sans-serif;
  line-height: 1.5;
}

/* Ohne diese zusätzliche Hülle geht es nicht: Der
   Bezugsrahmen muss ein Vorfahr sein. */
.huelle {
  position: relative;
  display: inline-block;
}

.button {
  padding: 8px 16px;
  border: 0;
  border-radius: 8px;
  font: inherit;
  color: #ffffff;
  background-color: #2563eb;
}

.tooltip {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 8px;
  width: max-content;
  max-width: 220px;
  padding: 8px 16px;
  border-radius: 8px;
  font-size: 14px;
  color: #fafafa;
  background-color: #18181b;
}

Die beiden Fassungen sehen gleich aus, der Unterschied steht im HTML. Die zweite braucht eine zusätzliche Hülle, die es nur gibt, damit CSS einen Bezugsrahmen hat. Das ist Markup ohne Bedeutung, und davon sammelt eine Seite im Lauf der Zeit einiges an.

Der Anker muss kein Vorfahre sein

Der Anker steht ganz woanders
body {
  margin: 0;
  padding: 24px;
  font-family: system-ui, sans-serif;
  line-height: 1.5;
}

.kopf {
  padding-bottom: 16px;
  border-bottom: 1px solid #d4d4d8;
}

.button {
  anchor-name: --konto;
  padding: 8px 16px;
  border: 0;
  border-radius: 8px;
  font: inherit;
  color: #ffffff;
  background-color: #2563eb;
}

.fuss {
  margin-top: 24px;
  color: #52525b;
  font-size: 14px;
}

.menue {
  position: absolute;
  position-anchor: --konto;
  position-area: block-end span-inline-end;
  margin-top: 8px;
  width: 200px;
  padding: 16px;
  border-radius: 8px;
  font-size: 14px;
  color: #18181b;
  background-color: #f4f4f5;
  box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
}

Hier steht das Menü ganz unten im Dokument und der Button ganz oben, und trotzdem hängt das Menü am Button. Mit position: absolute ist das schlicht nicht zu machen.

Praktisch wird das überall dort, wo das schwebende Element aus dem Fluss heraus muss, weil ein Vorfahre overflow: hidden hat. Ein Tooltip in einer scrollbaren Liste war bisher deshalb ein Fall für JavaScript. Man hat ihn an das Ende des Dokuments gehängt und seine Position bei jedem Scrollen neu ausgerechnet. Jetzt hängt er einfach am Anker.

Am Rand von selbst umklappen

Der zweite Grund für die Bibliotheken war der Bildschirmrand. Ein Tooltip, der rechts unten liegen soll, muss links oben liegen, wenn rechts unten kein Platz mehr ist.

position-try-fallbacks nimmt eine Liste von Ausweichlagen, und der Browser probiert sie der Reihe nach durch, bis eine passt. Für die häufigen Fälle gibt es fertige Werte wie flip-block und flip-inline, die die gewählte Lage einfach spiegeln. Zusammen mit dem popover-Attribut aus dem HTML-Kurs hast du so einen Tooltip zum Anklicken, und zwar ohne eine einzige Zeile JavaScript.

Erst nachsehen, dann benutzen

Ankerpositionierung ist gerade erst überall angekommen. Alle drei großen Browser können sie, Firefox als letzter seit Januar 2026. Sie ist also neu. Soll deine Seite auch auf einem zwei Jahre alten Gerät funktionieren, kannst du dich nicht darauf verlassen.

Damit ist sie ein Musterfall für Lektion 15.1. Erst kommt die Fassung, die überall läuft, dann @supports (anchor-name: --a) und darin die bessere. Der Fallback kostet nur die Hülle, die es sowieso schon gab, und dafür funktioniert die Seite in jedem Browser, auch in einem, der von Ankern noch nie etwas gehört hat. So ist auch die Aufgabe gebaut, dort mit anchor() statt position-area, und warum, steht im Aufgabentext.

Zum Mitnehmen

Ankerpositionierung richtet ein Element an einem anderen aus, ohne dass die beiden im Markup etwas miteinander zu tun haben. Der Anker bekommt mit anchor-name einen Namen mit zwei Strichen davor, das schwebende Element nennt ihn in position-anchor.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.