Describe the bug
On iOS 26, dismissing a keyboardType="numeric" keyboard (which has no return key — iOS 26 shows a floating system "Done" pill in the input-assistant row) produces a two-phase hide. The keypad visually slides away, but the keyboard frame reported to the library keeps an invisible ~48pt remnant (the input-assistant row that hosted the Done pill) for ~750ms–1s before iOS removes it.
During that window useReanimatedKeyboardAnimation() reports a stalled intermediate state — progress ≈ 0.134 (= 48/356), height ≈ -47.6 — and only then snaps to 0/0. Any KeyboardStickyView therefore settles in two visible steps: content comes to rest ~28pt too high (with our offset.opened), holds for up to a second with nothing visible at the bottom of the screen, then drops the remaining distance.
Code snippet
<KeyboardStickyView
enabled
offset={{ closed: 0, opened: 130 }}
>
{/* chart + form content */}
<TextInput keyboardType="numeric" returnKeyType="done" />
</KeyboardStickyView>
Instrumentation used to capture the values (UI-thread, rendered on screen and recorded at 30fps so the numbers are exact per frame):
const { height, progress } = useReanimatedKeyboardAnimation()
const debugProps = useAnimatedProps(() => {
const text = `p=${progress.value.toFixed(4)} h=${height.value.toFixed(1)}`
return { text, defaultValue: text }
})
To Reproduce
- Render a
KeyboardStickyView containing a TextInput with keyboardType="numeric" on iOS 26.
- Focus the input — the numeric keypad opens with the system's floating "Done" pill above it.
- Tap the Done pill (or otherwise blur the input).
- Watch the sticky content: it settles once as the keypad slides away, then shifts down again ~20–28pt about 1 second later.
Expected behavior
height/progress should reach 0 in one continuous animation when the keypad hides; the trailing invisible input-assistant view should not be reported as keyboard presence, so KeyboardStickyView settles in a single motion.
Screenshots
Recorded trace (values sampled every 250ms across three consecutive dismissals — the stall reproduces on every cycle):
p=1.0000 h=-356.0 keyboard fully open
p=0.1247 h=-44.4 hide animation runs
p=0.1335 h=-47.5 ← stalls here…
p=0.1335 h=-47.5 ← …for ~750ms–1s; keypad already visually gone,
p=0.0000 h=0.0 bottom of screen is empty during the stall
(Second and third cycles stall identically at p=0.1339 h=-47.7 and p=0.1337 h=-47.6. Frame captures confirm the bottom of the screen is empty during the stall, and the system Done pill even lingers on screen briefly after the keyboard reports closed.)
Smartphone (please complete the following information):
- Desktop OS: macOS 26 (Tahoe), Xcode 26 toolchain
- Device: physical iPhone and iPhone 17 simulator (reproduces on both; originally discovered on the physical device)
- OS: iOS 26 (26.4 on the simulator where the trace was captured)
- RN version: 0.85.3 (Expo SDK 56)
- RN architecture: new (Fabric)
- JS engine: Hermes
- Library version: 1.21.13 (changelogs through 1.22.2 don't appear to touch this)
Additional context
- 48pt matches the input-assistant row height that hosts iOS 26's floating "Done" pill on keypads without a return key; during the stall that view is registered in the keyboard frame but renders nothing.
- The stall length varies ~300ms–1s run to run; real finger taps on the Done pill reliably produce the ~1s hold.
- We first suspected our own TextInput mount/unmount churn during dismissal, but the stall reproduces with a completely static input hierarchy — the values above were captured with no React re-renders in flight.
- App-side workaround we shipped, in case it's useful to others: a wrapper around
KeyboardStickyView that cancels the net translation once the reported keyboard height is inside the remnant band:
const compensationStyle = useAnimatedStyle(() => {
const keyboardHeight = -height.value
const stickyTranslate =
height.value + interpolate(progress.value, [0, 1], [0, opened])
const stickyFactor = interpolate(keyboardHeight, [60, 160], [0, 1], "clamp")
return { transform: [{ translateY: -stickyTranslate * (1 - stickyFactor) }] }
})
- A possible library-level fix: when the remaining keyboard frame consists only of the input-assistant view (keypad view already gone), report the keyboard as hidden rather than holding the intermediate frame. Happy to test a patch.
Describe the bug
On iOS 26, dismissing a
keyboardType="numeric"keyboard (which has no return key — iOS 26 shows a floating system "Done" pill in the input-assistant row) produces a two-phase hide. The keypad visually slides away, but the keyboard frame reported to the library keeps an invisible ~48pt remnant (the input-assistant row that hosted the Done pill) for ~750ms–1s before iOS removes it.During that window
useReanimatedKeyboardAnimation()reports a stalled intermediate state —progress ≈ 0.134(= 48/356),height ≈ -47.6— and only then snaps to0/0. AnyKeyboardStickyViewtherefore settles in two visible steps: content comes to rest ~28pt too high (with ouroffset.opened), holds for up to a second with nothing visible at the bottom of the screen, then drops the remaining distance.Code snippet
Instrumentation used to capture the values (UI-thread, rendered on screen and recorded at 30fps so the numbers are exact per frame):
To Reproduce
KeyboardStickyViewcontaining aTextInputwithkeyboardType="numeric"on iOS 26.Expected behavior
height/progressshould reach0in one continuous animation when the keypad hides; the trailing invisible input-assistant view should not be reported as keyboard presence, soKeyboardStickyViewsettles in a single motion.Screenshots
Recorded trace (values sampled every 250ms across three consecutive dismissals — the stall reproduces on every cycle):
(Second and third cycles stall identically at
p=0.1339 h=-47.7andp=0.1337 h=-47.6. Frame captures confirm the bottom of the screen is empty during the stall, and the system Done pill even lingers on screen briefly after the keyboard reports closed.)Smartphone (please complete the following information):
Additional context
KeyboardStickyViewthat cancels the net translation once the reported keyboard height is inside the remnant band: