iOS Safari Tastatur position fixed: Composer sichtbar halten

Du fixierst einen Chat-Composer mit position: fixed; bottom: 0 am unteren Rand, tippst auf dem iPhone ins Textfeld, und die Tastatur schiebt sich darüber. Du tippst blind, oder Safari scrollt die Seite so, dass das Feld knapp über den Tasten auftaucht und der Header oben verschwindet. Das Problem „iOS Safari Tastatur position fixed“ ist kein Fehler in deinem CSS: Die Tastatur verkleinert einen anderen Viewport als den, an dem sich fixierte Elemente ausrichten. Dieses Tutorial erklärt die beiden Viewports, zeigt, warum CSS allein das auf iOS heute nicht löst, und baut einen Composer, der mit visualViewport und einem Motion Value auf der Tastatur mitfährt.
Der Beitrag gehört zu unserer Reihe Mobile UI in React. Der Guide zu 100vh auf dem Smartphone behandelt die Viewport-Einheiten, auf denen dieser Beitrag aufbaut.
Warum verdeckt die Tastatur auf iOS Elemente mit position fixed?
Weil die Seite zwei Viewports hat und die Tastatur nur einen davon verkleinert. Der Layout Viewport ist die Box, an der sich dein CSS ausrichtet: position: fixed, 100dvh und window.innerHeight beziehen sich alle auf ihn. Der Visual Viewport ist der Teil der Seite, den du tatsächlich siehst. Er schrumpft beim Pinch-Zoom oder wenn die Tastatur aufgeht.
Safari auf iOS lässt den Layout Viewport in voller Höhe, wenn die Tastatur erscheint, und verkleinert nur den Visual Viewport. Chrome für Android hat in Version 108 auf dasselbe Verhalten umgestellt (Ankündigung von Chrome (opens in a new tab)). Ein Element mit bottom: 0 bleibt also unten am Layout Viewport, und dort liegt jetzt die Tastatur.
Safari bringt noch einen zweiten Effekt mit. Damit das fokussierte Feld sichtbar bleibt, verschiebt Safari den Visual Viewport innerhalb des Layout Viewports nach unten, und visualViewport.offsetTop steigt über 0. Genau diese Verschiebung drückt einen fixierten Header beim Tippen oben aus dem Bild.
Solange ein Feld den Fokus hat, kannst du diese Werte lesen:
Die letzte Zeile ist schon der ganze Fix: Schieb die Leiste um genau diesen Wert nach oben.
Hält CSS allein eine Leiste über der iOS-Tastatur?
In Safari nicht, Stand Oktober 2026. Für das Problem „Tastatur verdeckt Input“ nennen die meisten Antworten im Netz eines von drei Werkzeugen auf CSS-Seite. Alle drei lassen iPhones aus:
- Viewport-Einheiten.
dvh,svhundlvhfolgen dem Layout Viewport, also schrumpft keine davon für die Tastatur. Die Höhe über der Tastatur, die du in iOS Safari brauchst, istvisualViewport.height, und die kann nur JavaScript lesen. interactive-widget=resizes-content. Dieser Schlüssel im Viewport-Meta-Tag sorgt dafür, dass die Tastatur den Layout Viewport verkleinert, sodass fixierte Leisten unddvhmitwandern. In Next.js ist dasinteractiveWidget: "resizes-content"imviewport-Export. caniuse (opens in a new tab) listet Chrome und Firefox für Android und keine Version von Safari auf iOS. Es hat einen Preis: Jedes Öffnen der Tastatur ändert jetzt die Größe der Seite, also bauen sich Layouts mitdvhneu auf, und alles, was an der Viewport-Größe hängt, springt.- Die VirtualKeyboard API. Mit
navigator.virtualKeyboard.overlaysContent = truekann CSSenv(keyboard-inset-height)lesen. MDN (opens in a new tab) kennzeichnet sie als experimentell, und caniuse (opens in a new tab) zeigt sie nur in Chromium-Browsern, nicht in Safari oder Firefox.
interactiveWidget kann trotzdem sinnvoll sein, wenn dein Traffic größtenteils von Android kommt, aber ein iPhone braucht Code. Dieser Code funktioniert auch auf Android mit der Standardeinstellung, also deckt ein Weg beide ab. Die beiden kommen sich auch nicht in die Quere: Mit resizes-content verkleinert Android innerHeight zusammen mit dem Visual Viewport, die Messung unten ergibt dort 0, und nichts wird doppelt angehoben.
Wie misst du die Tastatur mit visualViewport auf iOS?
Reagier auf die Events resize und scroll des Visual Viewports und auf Fokuswechsel, lies die drei Werte einmal pro Animation Frame und werte das Ergebnis nur aus, solange ein Textfeld den Fokus hat. Die Fokusprüfung ist wichtig: Auch ein Pinch-Zoom oder die einklappende Browserleiste ändern diese Werte, und beides soll deinen Composer nicht anheben. Die ganze Messung passt in einen einzigen Hook:
Ein paar Details darin sind Absicht:
- Die Schwelle von 80px. Kleine Änderungen am Visual Viewport kommen von der Browseroberfläche, nicht von einer Tastatur. Alles unter der Schwelle zählt als 0.
viewport.scale. Bei Pinch-Zoom ist der Visual Viewport kleiner, aus einem Grund, der nichts mit der Tastatur zu tun hat.requestAnimationFrame. Die Tastatur ist animiert, und Safari kann mehrere Events pro Frame feuern. Einmal pro Frame zu messen beschränkt die Arbeit auf einen Lese- und einen Schreibzugriff.- Alles in
useEffect. Auf dem Server gibt es keinwindow, und der erste Render im Client muss zum Server-HTML passen, also startet die Höhe bei 0.
Warum bewegst du die Leiste mit einem Motion Value statt mit State?
Weil sich der Wert in jedem Frame ändert, während die Tastatur hereingleitet. Ein setState pro Frame rendert den Composer und alles darunter in jedem dieser Frames neu, je nach Display bis zu 60 oder 120 Mal pro Sekunde. Ein Motion Value aus motion/react umgeht React: Du gibst ihn an style, und Motion schreibt den Transform direkt ins Element.
translateY verschiebt die Leiste ohne Layout-Durchlauf, also gibt es auf dem Rest der Seite keinen Reflow, während sich die Tastatur bewegt. Der Input nutzt text-base (16px), denn Safari auf iOS zoomt in jedes fokussierte Feld mit kleinerer Schrift hinein. Das untere Padding hält bei geschlossener Tastatur Abstand zum Home-Indikator; Tailwind Safe Area Insets erklärt das max().
Was bringt der Hook useKeyboardInset zusätzlich?
Der Hook useKeyboardInset von Wingo UI ist dieselbe Messung mit zwei Extras: ein Flag open, das nur beim Öffnen oder Schließen der Tastatur neu rendert, und optional cssVar, das die Höhe in eine CSS-Variable auf <html> schreibt, praktisch für Layouts, die sich in CSS leichter umsetzen lassen. Das Flag open behebt ein Detail, das der Composer oben falsch macht: Sobald die Tastatur oben ist, liegt der Home-Indikator dahinter, und das Safe-Area-Padding wird zur Lücke zwischen Leiste und Tasten.
$ npx wingo-ui@latest add use-keyboard-insetInstallier ihn mit npx wingo-ui@latest add use-keyboard-inset. Falls dein Projekt motion noch nicht hat, kommt es dabei mit.
Wie bleibt die letzte Nachricht über einem Sticky Composer sichtbar?
Gib dem scrollenden Inhalt unten ein Padding in Höhe von Composer plus Tastatur, und scroll ans Ende, wenn die Tastatur aufgeht. Diesen Teil vergessen die meisten Setups mit Sticky Composer: Die Leiste wandert nach oben, aber die neueste Nachricht bleibt dort, wo jetzt die Tastatur ist.
Der Prompt Input übernimmt die Leiste für dich. Mit position="fixed" pinnt er sich an den unteren Rand, hebt sich über useKeyboardInset ohne Re-Render über die Tastatur, hält Abstand zum Home-Indikator und schreibt seine eigene Höhe in --prompt-input-offset auf <html>. Füg die Höhe der Tastatur als zweite Variable hinzu, dann kann die Seite ihr Padding in CSS setzen:
Gescrollt wird hier das Fenster selbst. So bleiben Safaris eigenes Scrollen, das Hochscrollen per Tipp auf die Statusleiste und das Einklappen der Toolbar beim Scrollen erhalten. Auf dem Smartphone erzeugt Enter im Prompt Input eine neue Zeile, und der runde Button sendet, ohne die Tastatur zu schließen. So lassen sich mehrere Nachrichten hintereinander schicken.
$ npx wingo-ui@latest add prompt-inputWas gilt für ein Bottom Sheet mit Textfeld?
Ein Sheet ist ebenfalls ein fixiertes Element, also verdeckt die Tastatur seinen Footer und jedes Feld in seiner unteren Hälfte. Der kostenlose Drawer nutzt die Option repositionInputs von vaul, die standardmäßig aktiv ist: Solange ein Feld darin den Fokus hat, hebt er das Sheet um die verdeckte Höhe an und verkleinert es, wenn es nicht mehr über die Tastatur passt. Unser Guide zum React Bottom Sheet zeigt, wie das mit Snap Points zusammenspielt.
$ npx wingo-ui@latest add drawerSchalte repositionInputs nur ab, wenn sich das Sheet selbst um die Tastatur kümmert, etwa ein Kommentar-Sheet, dessen Composer schon useKeyboardInset folgt. Zwei Systeme, die dasselbe Sheet bewegen, arbeiten gegeneinander.
Auf welche iOS-Tastatur-Bugs solltest du testen?
Die Messung ist einfach; an den Randfällen scheitern Apps. Teste sie auf einem echten iPhone oder im iOS Simulator mit getrennter Hardware-Tastatur, damit die Bildschirmtastatur erscheint:
- Der Header rutscht weg. Weil Safari den Visual Viewport nach unten verschiebt, verlässt ein fixierter Header den oberen Rand. Wenn er bleiben muss, schieb ihn um
visualViewport.offsetTopnach unten, genauso wie du den Composer nach oben schiebst. - Die Tastatur geht zu, die Leiste bleibt oben. Der Hook zählt den Inset nur, solange ein Textfeld den Fokus hat. Bei
focusoutfällt er auf 0, und die Leiste kehrt nach unten zurück, auch wenn die Viewport-Werte der schließenden Tastatur hinterherhinken. Eine Version, die nur das Eventresizeliest, lässt die Leiste womöglich in der Luft hängen. - Offsets, die sich nie zurücksetzen. Im Apple Developer Forum (opens in a new tab) haben Entwickler berichtet, dass manche Builds von iOS 26.0 nach dem Schließen der Tastatur
visualViewport.offsetTopüber 0 oder die Höhe unterinnerHeightließen, sodass fixierte Leisten versetzt saßen. WebKit-Bug 297779 (opens in a new tab), im August 2025 gegen eine Beta von iOS 26 eingereicht, sammelt dasselbe Symptom, darunter einen Bericht überoffsetTop, das bei 24px hängen blieb. Laut Kommentaren dort ist es in iOS 26.1 für manche Seiten behoben und auf anderen noch da. Wenn du bei jedem Event neu rechnest, statt den ersten Wert zu cachen, arbeitest du immer mit dem, was der Browser gerade meldet, und die Fokusprüfung ignoriert veraltete Zahlen, sobald kein Feld mehr den Fokus hat. - Pinch-Zoom. Zoom hinein und fokussier dann das Feld. Die Prüfung von
scalehält die Leiste an ihrem Platz, statt sie springen zu lassen. - Felder ohne Tastatur. Eine Checkbox oder ein Range-Input im Composer bekommt den Fokus, ohne eine Tastatur zu öffnen; die Typenliste schließt sie aus.
- Desktop und iPad mit Hardware-Tastatur. Ohne Bildschirmtastatur schrumpft der Visual Viewport nicht, also bleibt der Inset bei 0 und nichts bewegt sich.
Im Device Mode von Chrome auf dem Desktop kannst du das nicht testen, denn dort gibt es keine Bildschirmtastatur. In automatisierten Tests prüfst du das Layout bei Inset 0 und deckst den Tastaturpfad vor jedem Release mit einem manuellen Durchgang auf einem Gerät ab.
Womit fängst du an?
Wenn du nur eine fixierte Leiste hast, kopier den Hook useKeyboardHeight von oben und gib der Leiste einen y-Transform: Das sind etwa 50 Zeilen, und außer motion braucht er nichts. Baust du einen Chat-Screen, übernimmt der Prompt Input mit position="fixed" das Anheben, die Safe Area und die Höhenvariable für dich, und der Guide zur React AI Chat UI deckt den Rest des Screens ab. useKeyboardInset und der Prompt Input gehören zu Wingo UI Pro; der Drawer ist kostenlos mit npx wingo-ui@latest add drawer.
Häufige Fragen
Warum funktioniert position fixed mit der Tastatur in iOS Safari nicht?
Es funktioniert, nur gegen die falsche Box. Safari lässt den Layout Viewport beim Öffnen der Tastatur in voller Höhe und verkleinert nur den Visual Viewport. Fixierte Elemente richten sich am Layout Viewport aus, also liegt bottom: 0 hinter der Tastatur.
Ändert sich 100dvh, wenn die iOS-Tastatur aufgeht?
Nein. Viewport-Einheiten folgen dem Layout Viewport, und den verändert die Tastatur weder in Safari auf iOS noch standardmäßig in Chrome für Android seit Version 108. Die sichtbare Höhe liest du aus window.visualViewport.
Funktioniert interactive-widget=resizes-content auf dem iPhone?
Nein. Stand Oktober 2026 listet caniuse Unterstützung in Chrome und Firefox für Android und keine in Safari auf iOS. iPhones brauchen also weiterhin die Messung über visualViewport.
Kann ich auf iOS die VirtualKeyboard API und keyboard-inset-height nutzen?
Nein. Die VirtualKeyboard API und ihre Variablen env(keyboard-inset-*) gibt es nur in Chromium-Browsern; caniuse zeigt Stand Oktober 2026 keine Unterstützung in Safari oder Firefox.
Warum ist mein fixierter Header auf iOS 26 verschoben, nachdem die Tastatur zugegangen ist?
Manche Builds von iOS 26.0 lassen visualViewport.offsetTop nach dem Schließen der Tastatur über 0 oder visualViewport.height unter innerHeight, wie im Apple Developer Forum und in WebKit-Bug 297779 berichtet. Rechne bei jedem resize- und scroll-Event von visualViewport neu, statt einen Wert zu cachen, und ignorier die Zahlen, solange kein Textfeld den Fokus hat.
- iOS Safari
- Tastatur
- visualViewport
- Mobile
- React


