TL;DR: Ein Banner unter der Navigation maß 549px Breite in einem 375px-Handy-Viewport. Sein Titel hatte
flex-shrink: 0, blieb also 399px breit, quetschte die Wertespalte auf null und drückte die wichtigste Schaltfläche aufx=452— über den rechten Rand. Weil die Seiteoverflow-x: hiddennutzte, gab es keine Scrollbar: Der Button war schlicht weg. Der Fix war ein Mobile-only-Grid mitminmax(0, 1fr), ein Tap-Target für den Schließen-Button und eine sticky Action-Zeile für das aufgeklappte Panel.
Die Ausgangslage
Direkt unter der Navigation sitzt ein einzeiliges Status-Banner. Es sagt in kompakten Monospace-Chips ein paar Dinge über die aktuelle Sitzung und bietet einen "Alle Daten ansehen"-Call-to-Action, der ein Panel mit der vollständigen Aufschlüsselung öffnet. Am Desktop ist es eine saubere Reihe. Es wurde reviewt, es bestand, und niemand hatte Grund zu zweifeln.
Dann zog ich das Fenster auf die Handy-Breite, auf der die meisten Besucher tatsächlich ankommen — und die Reihe fiel leise auseinander. Nicht mit einem Fehler, nicht mit einer Scrollbar, sondern mit einem fehlenden Button. Wenn ein Control verschwindet, ohne den Build zu brechen, überlebt es jedes Review, das nur einen Desktop-Screenshot ansieht.
Die Messwerte unten kommen aus einem echten headless Chrome über das DevTools-Protokoll, das die Geometrie des Live-DOM ausliest. Es gibt keinen Ersatz dafür, die Elemente zu messen, die man tatsächlich ausgeliefert hat.
Das Muster
Ein nowrap-Titel, der nicht schrumpfen darf, garantiert Overflow
Das Titel-Element trug zwei völlig vernünftig wirkende Deklarationen:
.barTitle { flex-shrink: 0; white-space: nowrap; }
Jede für sich ist in Ordnung. Zusammen, in einer Flex-Reihe, sind sie eine Falle: Der Titel weigert sich zu schrumpfen und weigert sich umzubrechen, bleibt auf einem schmalen Screen also breiter als der Platz, den er bekommen hat. Alles danach bekommt den Rest — und der ist nichts.
Eine Flex-Spalte hätte minmax(0, 1fr) sein müssen
Die Wertespalte war flex: 1. Flex-Items haben eine automatische Mindestgröße, ein flex: 1-Item schrumpft also nicht unter seine Inhaltsbreite, solange man es nicht explizit erlaubt. Der Titel fraß die Reihe, die Wertespalte kollabierte auf 0px, und die rechts fixierte Action wurde über den Rand gedrückt. Der mobile-taugliche Fix ist, die mittlere Spur wirklich schrumpfen zu lassen — mit Grid: minmax(0, 1fr).
overflow-x: hidden machte aus außerhalb ein: verschwunden
Das globale Stylesheet schützt vor horizontalem Scroll mit overflow-x: hidden. Genau dieser Schutz versteckt diese Bug-Klasse: body.scrollWidth meldete 375, für eine automatische Prüfung sah also nichts falsch aus, während der getBoundingClientRect().right der Action bei 549 lag. Es gab keine Scrollbar, die es verraten hätte, und keine Konsolenwarnung, die es gemeldet hätte. Ein Control, das existiert, fokussierbar ist und trotzdem nicht erreichbar ist, ist kein kosmetisches Problem — erst recht nicht, wenn es ein Datenschutz-Control ist.
Mobile-Bugs kommen im Paar
Der Fix der kollabierten Leiste legte einen zweiten, leiseren Fehler frei: das aufgeklappte Panel. Seine Action-Buttons — die, mit denen ein Nutzer das Panel schließen oder einklappen kann — lagen rund 2000px unterhalb des Folds, ganz unten in einem langen Scroll. Auf dem Handy sind "die Buttons funktionieren" und "die Buttons sind erreichbar" zwei verschiedene Aussagen.
Die Messung, vorher und nachher
| Viewport | Vorher | Nachher |
|---|---|---|
| 375px | scrollWidth 549; rechter Rand der Action 549 (außerhalb); Wertespalte 0px breit; Close-Target 24×24 | scrollWidth 375; rechter Rand der Action 329; Wertespalte 221px; Close-Target 32×32; Titel bricht auf zwei Zeilen |
| 768px | — | Balkenhöhe 66px, jedes Control im Viewport |
| 1280px | Eine Reihe, Höhe 37px | Eine Reihe, Höhe 37px — Desktop pixelgleich |
Der Fix ist ein reines Mobile-Layout:
@media (max-width: 855px) {
.barMain {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
flex-basis: calc(100% - 2.75rem);
}
.dot { grid-area: 1 / 1; }
.barTitle { grid-area: 1 / 2 / 2 / 4; white-space: normal; min-width: 0; }
.barValues { grid-area: 2 / 1 / 3 / 3; }
.barAction { grid-area: 2 / 3; justify-self: end; }
.barClose { width: 32px; height: 32px; }
.actions { position: sticky; bottom: 0; }
}
Der Angriffspfad eines verschwindenden Controls
Reproduktion im Labor
Jede Prüfung unten zielt auf eine Seite, die ich lokal kontrolliere. Es geht nicht um das Banner — es geht um die Messtechnik.
# 1. die Geometrie des tatsächlich ausgelieferten Controls je Viewport auslesen
node cdp-measure.js --viewport 375x812
# <- Dump: Viewport, body.scrollWidth und das Rect jedes Elements
# 2. die entscheidende Zusicherung: nichts darf die Viewport-Breite überschreiten
node -e "const r=require('./rects.json'); \
const over=r.filter(e=>e.right>r.viewport); \
if(over.length){console.error('OFFSCREEN',over);process.exit(1)}"
# <- schlägt laut fehl, wenn ein Control jenseits des rechten Rands liegt
# 3. prüfen, ob der Schutz das Problem versteckt
node -e "const d=await page.evaluate(()=>({ \
scrollW: document.scrollingElement.scrollWidth, \
overflowX: getComputedStyle(document.documentElement).overflowX }));"
# <- scrollW entspricht dem Viewport, WEIL overflow-x hidden ist, nicht weil es passt
# die Messung ist der Test:
# für jeden Breakpoint (320, 375, 768, 1024, 1280):
# jedes interaktive Element muss im Bereich 0 .. Viewport-Breite liegen
# die Action-Zeile muss erreichbar sein, ohne unter den Fold zu scrollen
# Tap-Targets müssen im schmalsten Layout >= 44px sein
Erkennung im eigenen Bestand
- Messen statt schiefen. Ein Desktop-Screenshot beweist nichts über das Layout, das bei 375px rendert. Eine echte Engine steuern und
getBoundingClientRect()auslesen. - Gegen den Viewport zusichern, nicht gegen einen Screenshot. Die Prüfung fehlschlagen lassen, wenn der
right-Wert irgendeines interaktiven Elements die Viewport-Breite überschreitet — dann kann Overflow kein Review mehr passieren. overflow-x: hiddennicht vertrauen. Es versteckt das Symptom. WennscrollWidthnur deshalb dem Viewport entspricht, weil Overflow geclippt wird, bist du nicht sicher — du bist blind.- Umbrechen als Feature behandeln.
flex-shrink: 0pluswhite-space: nowrapist ein Versprechen, das man auf kleinen Screens nicht halten kann. Greif zuminmax(0, 1fr)und lass die Tagline umbrechen. - Den aufgeklappten Zustand testen, nicht nur den eingeklappten. Lange Panels verstecken ihre Action-Buttons am Ende eines Scrolls; eine sticky Action-Zeile hält sie erreichbar.
- Die Tap-Targets gleich mitfixen. Ein 24×24-Close-Button unter der 44px-Empfehlung ist ein zweiter Bug im selben Component.
Behebung
# sofort
1. der mobilen Leiste ein Grid geben: auto | minmax(0, 1fr) | auto
2. den Titel umbrechen lassen: white-space normal; min-width 0
3. die Action im Layout halten: justify-self end, nie margin-left auto
4. den Close-Button auf ein 32px+ Tap-Target heben; 44px, wo Platz ist
# innerhalb einer Woche
5. die Panel-Actions stickiy am unteren Viewport-Rand fixieren
6. 320 / 375 / 768 / 1024 / 1280 nachmessen und zusichern, dass nichts die Breite überschreitet
7. die Zusicherung in CI aufnehmen, damit die nächste Template-Änderung sie nicht wieder öffnet
# innerhalb eines Monats
8. jede weitere fixed-width-nowrap-Chip-Reihe im Designsystem auditieren
9. prüfen, dass Datenschutz- und Consent-Controls im schmalsten Layout erreichbar sind
Was es kostet
Das ist kein kosmetischer Fund. Das Control, das den Bildschirm verließ, war der Einstieg in die Detailansicht des Banners — dorthin, wo ein Besucher sieht und handelt, was gesammelt wurde. Ein Datenschutz- oder Consent-Control, das der Nutzer nicht erreicht, ist ein kaputtes Control, und ein kaputtes Control ausgerechnet auf der Seite, die Transparenz verspricht, ist der schlechteste Ort dafür.
- Betroffen sind die Nutzer auf den kleinsten Screens — Mobile ist die Mehrheit des Traffics, kein Randfall.
- Der Fehler ist lautlos: kein Fehler, keine Scrollbar, keine Konsolenwarnung. Er versteckt sich vor Reviews, die einem Desktop-Render vertrauen.
- Es ist eine Klasse von Bug, kein Einzelfall. Jede
flex-shrink: 0+nowrap-Chip-Reihe im selben Designsystem hat denselben latenten Fehler.
Fazit — was ich daraus mitnehme
- Miss das Layout, das du ausgeliefert hast, in den Breiten, die deine Besucher nutzen. Ein Screenshot in einer Größe ist eine Annahme, kein Beweis.
flex-shrink: 0undwhite-space: nowrapsind in Ordnung, bis der Screen schmal wird. Auf Mobile fressen Titel so Buttons.overflow-x: hiddenbehebt Overflow nicht; es versteckt den Beweis. Sichere stattdessen Element-Rechtecke gegen den Viewport zu.- Wenn ein Control ein Datenschutz- oder Consent-Control ist, gehört "auf Mobile erreichbar" zur Definition, nicht zum Nice-to-have.
- Fix den Breakpoint, dann sichere ihn ab. Dieselbe Reihe kommt zurück, sobald jemand das Template bearbeitet.