Jeder kennt diesen Effekt, auch wenn er einem selten bewusst auffällt: Man fährt mit der Maus über ein Navigationsmenü, der Eintrag wird fett, und das ganze Menü zuckt. Die Nachbarn rücken ein paar Pixel zur Seite, ein Text bricht plötzlich anders um, eine rechtsbündige Zahl hüpft. Es ist nur ein Detail, aber es lässt eine Seite unruhig und billig wirken.
In diesem Beitrag geht es darum, warum das passiert und wie man mit einem simplen text-shadow Text fett wirken lässt, ohne dass sich auch nur ein Pixel im Layout verschiebt.
Warum fetter Text springt
Ein fetter Schriftschnitt ist nicht einfach „dieselben Buchstaben mit dickeren Strichen“. Bold-Schnitte haben breitere Glyphen und eine größere Laufweite: Ein „m“ in Bold ist deutlich breiter als ein „m“ in Regular. Nutzt man
.nav a:hover {
font-weight: 700;
}
dann wird der Link breiter. In einer Zeile mit display: flex oder inline schiebt er alle folgenden Elemente nach rechts. In einer Box mit fester Breite kann er sogar umbrechen, und dann ändert sich auch die Höhe.
Mit der Maus über die Einträge fahren. Die roten Linien zeigen, wo jeder Eintrag im Ruhezustand beginnt.
Typische Stellen, an denen das stört:
- Navigationsleisten und Tabs (aktiver Tab)
- Listen mit Auswahl, etwa Ordner oder Filter in einer Seitenleiste
- Buttons, deren Breite sich nach dem Text richtet
- Tabellenzeilen, die beim Hover hervorgehoben werden
Die Idee: Fettdruck vortäuschen
Der Trick ist alt, viel älter als CSS. Schreibmaschinen und Nadeldrucker haben Fettdruck erzeugt indem sie dasselbe Zeichen noch einmal anschlagen, minimal versetzt. Das Ergebnis wirkt kräftiger, belegt aber exakt denselben Platz.
In CSS gibt es dafür ein Werkzeug, das genau diese Eigenschaft hat: text-shadow. Schatten nehmen keinen Platz im Layout ein. Sie verändern weder Breite noch Höhe einer Box und lösen keinen Umbruch aus. Gibt man dem Schatten die Textfarbe und versetzt ihn um einen Bruchteil eines Pixels, verschmilzt er mit den Buchstaben und macht jeden Strich etwas dicker.
.nav a:hover,
.nav a[aria-current="page"] {
text-shadow: 0.5px 0 0 currentColor;
}
Das ist im Kern schon alles. currentColor sorgt dafür, dass der „Schatten“ immer die Farbe des Texts hat, auch im Dark Mode oder wenn sich die Linkfarbe beim Hover ändert.
Mit der Maus über die Einträge fahren. Die roten Linien zeigen, wo jeder Eintrag im Ruhezustand beginnt.
Feinjustierung
Ein einzelner Schatten nach rechts macht die Striche asymmetrisch dicker. Für einen gleichmäßigeren Eindruck verteilt man zwei Schatten auf beide Seiten:
.nav a:hover {
text-shadow:
0.35px 0 0 currentColor,
-0.35px 0 0 currentColor;
}
Den Wert sollte man nach Schriftgröße und Schrift wählen:
| Schriftgröße | Versatz (pro Seite) |
|---|---|
| bis ca. 14px | 0.25px–0.35px |
| 16px–20px | 0.35px–0.5px |
| große Überschriften | 0.5px–1px |
Das sind Richtwerte zum Ausprobieren, keine festen Regeln. Auf hochauflösenden Displays sehen kleine Bruchteile feiner aus, auf normalen Monitoren werden sie stärker geglättet. Am besten probiert man es auf beiden aus.
Mit der Maus über die Einträge fahren. Die roten Linien zeigen, wo jeder Eintrag im Ruhezustand beginnt.
Sanfter Übergang mit ease
Ein schöner Nebeneffekt: text-shadow lässt sich animieren. Mit einer Transition wird der Text beim Hover sanft kräftiger und beim Verlassen wieder dünner, statt hart umzuschalten:
.nav a {
transition: text-shadow 0.25s ease;
}
.nav a:hover {
text-shadow:
0.4px 0 0 currentColor,
-0.4px 0 0 currentColor;
}
Wichtig ist, dass die transition auf dem Element im Ruhezustand steht und nicht nur in :hover. Nur so wird auch das Zurückgehen animiert. Damit es weich wirkt, braucht auch der Ruhezustand einen vergleichbaren Wert. Ohne text-shadow interpoliert der Browser von „kein Schatten“ aus, das klappt in aktuellen Browsern, aber text-shadow: 0 0 0 currentColor als Ausgangswert macht es eindeutig.
Bei der Dauer reichen 150 bis 300 Millisekunden. Kürzer wirkt wie ein hartes Umschalten, länger fühlt sich träge an, besonders wenn man schnell über mehrere Einträge fährt. ease beginnt schnell und läuft weich aus, das passt gut zu Hover-Effekten. ease-out ist eine gute Alternative.
Bei font-weight geht das mit klassischen Schriften nicht, dort springt der Wert einfach von 400 auf 700. Und selbst wenn eine variable Schrift die Strichstärke stufenlos animieren kann, wächst dabei die Breite mit: Der Text springt dann nicht mehr, sondern rutscht, und die Nachbarn rutschen mit.
Mit der Maus über die Einträge fahren. Die roten Linien zeigen, wo jeder Eintrag im Ruhezustand beginnt.
Dein System wünscht reduzierte Bewegung, deshalb ist der Übergang hier abgeschaltet (prefers-reduced-motion: reduce).
Reduzierte Bewegung respektieren
Manche Menschen reagieren empfindlich auf Bewegung am Bildschirm, etwa bei Migräne, Schwindel oder vestibulären Störungen. Für sie gibt es in jedem Betriebssystem eine Einstellung, die Animationen reduziert, und der Browser meldet sie über den Media Query prefers-reduced-motion. Eine Seite, die ihn beachtet, schaltet Übergänge für diese Nutzer ab:
@media (prefers-reduced-motion: reduce) {
.nav a {
transition: none;
}
}
Der Effekt selbst bleibt erhalten: Der Text wird weiterhin fett, nur eben sofort statt eingeblendet. Genau das ist der Sinn: reduce heißt „weniger Bewegung“, nicht „keine Hervorhebung“.
Ein dezenter Schatten über eine Viertelsekunde ist sicher keine besonders belastende Animation. Der Media Query kostet aber nur drei Zeilen, und wer ihn bei kleinen Effekten von Anfang an mitschreibt, vergisst ihn bei großen nicht.
Zum Testen muss man die Systemeinstellung nicht umstellen:
- Chrome und Edge: DevTools → Menü „More tools“ → Rendering → „Emulate CSS media feature prefers-reduced-motion“ →
reduce - Firefox:
about:config→ui.prefersReducedMotionauf1 - Systemweit: Windows 11: Einstellungen → Barrierefreiheit → Visuelle Effekte → Animationseffekte aus; Windows 10: Einstellungen → Erleichterte Bedienung → Anzeige → „Animationen in Windows anzeigen“ aus; macOS: Bedienungshilfen → Anzeige → Bewegung reduzieren
Ein vollständiges Beispiel
<nav class="nav">
<a href="/" aria-current="page">Start</a>
<a href="/artikel">Artikel</a>
<a href="/projekte">Projekte</a>
<a href="/kontakt">Kontakt</a>
</nav>
.nav {
display: flex;
gap: 1.5rem;
}
.nav a {
color: inherit;
text-decoration: none;
transition: text-shadow 0.25s ease;
}
.nav a:hover,
.nav a:focus-visible,
.nav a[aria-current="page"] {
text-shadow:
0.4px 0 0 currentColor,
-0.4px 0 0 currentColor;
}
@media (prefers-reduced-motion: reduce) {
.nav a {
transition: none;
}
}
Fährt man über „Artikel“, wird es kräftiger, und „Projekte“ und „Kontakt“ bleiben genau dort, wo sie sind.
Mit der Maus über die Einträge fahren. Die roten Linien zeigen, wo jeder Eintrag im Ruhezustand beginnt.
Das vollständige Beispiel, „Start“ ist die aktuelle Seite
Grenzen
Der Trick ist kein vollwertiger Ersatz für einen echten Bold-Schnitt:
- Es ist ein Imitat. Ein echter Bold-Schnitt ist vom Schriftdesigner gezeichnet, mit angepassten Innenräumen und Proportionen. Der Schatten macht nur alle Striche gleichmäßig dicker. Für eine Hervorhebung reicht das meist, für ganze Absätze in Fett nicht.
- Zu viel verschmiert. Bei großen Werten laufen Innenräume von „e“, „a“ oder „g“ zu, und der Text wird matschig.
- Barrierefreiheit: Im Modus für erzwungene Farben (z. B. Windows-Kontrastdesigns) entfernen Browser
text-shadow. Die Hervorhebung ist dann unsichtbar. Wer sich auf sie verlässt, etwa für den aktiven Menüpunkt, sollte dort ein anderes Signal zeigen:
@media (forced-colors: active) {
.nav a[aria-current="page"] {
text-decoration: underline;
}
}
Und grundsätzlich gilt: Der aktive Menüpunkt sollte nie nur über die Strichstärke erkennbar sein. aria-current="page" hilft Screenreadern, eine Unterstreichung oder ein Marker hilft allen anderen.
Alternativen
Der Schatten ist nicht der einzige Weg, aber oft der einfachste. Kurz die anderen:
Platz für die fette Breite reservieren. Man rendert den Text zusätzlich unsichtbar in Bold, damit das Element von Anfang an die fette Breite hat:
.nav a {
display: inline-flex;
flex-direction: column;
}
.nav a::after {
content: attr(data-text);
font-weight: 700;
height: 0;
overflow: hidden;
visibility: hidden;
}
.nav a:hover {
font-weight: 700;
}
Das sieht echt fett aus, braucht aber den Text doppelt im HTML (data-text="Artikel"), und die Einträge stehen auch ohne Hover in der breiteren Bold-Breite. Ein Text, der linksbündig steht, „wächst“ beim Hover außerdem sichtbar nach rechts in den reservierten Platz.
Mit der Maus über die Einträge fahren. Die roten Linien zeigen, wo jeder Eintrag im Ruhezustand beginnt.
Reservierte Bold-Breite (::after): springt nicht, aber breiter im Ruhezustand
**-webkit-text-stroke.** Eine Kontur um die Buchstaben, ebenfalls ohne Einfluss auf das Layout. Sie liegt aber mittig auf der Kontur und frisst bei kleinen Größen die Innenräume schneller zu als ein Schatten.
Variable Fonts mit Grad-Achse. Manche variablen Schriften (etwa Roboto Flex) haben eine Achse GRAD, die die Strichstärke ändert, ohne die Breite zu verändern:
.nav a:hover {
font-variation-settings: "GRAD" 150;
}
Das ist die sauberste Lösung, funktioniert aber nur mit Schriften, die diese Achse auch haben. Die normale wght-Achse ändert die Breite dagegen genauso wie ein fester Bold-Schnitt.
Fazit
Wenn Text beim Hervorheben nicht springen soll, ist font-weight das falsche Werkzeug, denn es ändert die Breite. Ein text-shadow in currentColor mit einem Bruchteil eines Pixels Versatz lässt Text fett wirken, ohne das Layout zu ändern. Er ist mit einer Zeile CSS erledigt, lässt sich animieren und funktioniert mit jeder Schrift. Für echte, längere Fettpassagen bleibt der Bold-Schnitt die richtige Wahl, für Hover und Auswahl ist der Schatten fast immer die bessere Wahl.
