Ankerlinks unter einem klebenden Header – scroll-margin-top rettet die Überschrift
Du klickst im Inhaltsverzeichnis auf einen Abschnitt – und der klebende Header verdeckt genau die Überschrift, zu der du wolltest. Zwei CSS-Zeilen lösen das, ganz ohne JavaScript und ohne unsichtbare Platzhalter-Elemente.
Fast jede Seite mit klebendem Kopfbereich hat diesen Fehler: Ein Link auf #preise springt zum Abschnitt, der Browser stellt die Überschrift exakt an den oberen Fensterrand – und der 64 Pixel hohe Header liegt darüber. Die Überschrift ist weg, man landet mitten im Text.
Früher half man sich mit leeren <span>-Ankern oder negativen Rändern. Heute gibt es eine Eigenschaft, die genau dafür gemacht ist.
scroll-margin-top: der Abstand, den der Browser beim Springen einhält
:target,
h2[id],
h3[id],
section[id] {
scroll-margin-top: 5rem; /* Höhe des Headers plus etwas Luft */
}scroll-margin wirkt nur beim Scrollen zu diesem Element – im Layout ändert sich nichts, keine Lücke, keine verschobene Box. Das gilt für Ankerlinks, für element.scrollIntoView() und für das Wiederherstellen der Scrollposition.
Die Header-Höhe nur einmal aufschreiben
:root {
--header-h: 64px;
}
.site-header {
position: sticky;
top: 0;
height: var(--header-h);
}
[id] {
scroll-margin-top: calc(var(--header-h) + 12px);
}Ändert sich die Höhe des Headers, stimmt der Sprung automatisch mit – eine Zahl, eine Stelle.
Sanft statt springend
@media (prefers-reduced-motion: no-preference) {
html { scroll-behavior: smooth; }
}Bewusst in der Media Query: Weiches Scrollen über lange Strecken ist genau die Bewegung, die manchen Menschen unangenehm wird. Wer „Bewegung reduzieren" eingestellt hat, bekommt den normalen Sprung.
Fallstrick
Zwei Dinge gehen hier gern schief. Erstens: scroll-behavior gehört ans scrollende Element. Das ist meistens html – wenn dein Layout aber einen eigenen Rollbereich hat (overflow: auto), muss die Eigenschaft dorthin. Zweitens: scroll-margin-top hilft nichts, wenn du den Sprung selbst mit JavaScript rechnest (window.scrollTo(0, el.offsetTop)). Nutze el.scrollIntoView() – das respektiert die Eigenschaft.
Sauber bedienbare Navigation gehört für mich zum Handwerk, nicht zur Kür. Wenn du eine Seite brauchst, die auch mit Tastatur und Sprungmarken funktioniert: bymw.de.
Quellen
Du brauchst mehr als ein Snippet?
Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.
Projekt anfragen →Verwandte Snippets
:is() und :where() – kürzere Selektoren, und einer davon wiegt nichts
Drei fast gleiche Selektoren untereinander, nur weil die Überschrift mal h2 und mal h3 heißt? :is() fasst das zusammen. Und :where() macht dasselbe, ohne die Spezifität hochzutreiben – der Unterschied ist der ganze Trick.
content-visibility – lange Seiten rendern, ohne dass der Browser alles auf einmal baut
Bei sehr langen Seiten rechnet der Browser Layout und Malen für jeden Abschnitt – auch für die, die 8.000 Pixel weiter unten liegen. Eine CSS-Eigenschaft verschiebt diese Arbeit auf den Moment, in dem sie gebraucht wird.
color-mix() – Hover- und Dark-Mode-Farben ausrechnen lassen, statt sie zu pflegen
Für jede Markenfarbe eine hellere, eine dunklere und eine transparente Variante von Hand zu pflegen, ist Fleißarbeit mit Fehlerquote. color-mix() mischt sie im Browser – aus genau einer Quellfarbe.