Keyboard-avoiding view

<keyboard-avoiding-view> moves its content clear of the keyboard, by padding (behavior="padding", the default here), shrinking ("height") or shifting ("position").

Import
import { KeyboardDock, KeyboardAvoidingView } from '@ng-native/components';
Template
<keyboard-dock><keyboard-avoiding-view> once they are in the component's imports
<keyboard-avoiding-view class="flex-1">
  <text-input placeholder="Message" [formField]="message" />
</keyboard-avoiding-view>

There is no native component behind this on either platform. It measures its own frame against the keyboard's reported height and adjusts a style, which is also why it does nothing useful off a device with no keyboard to avoid.

How the overlap is measured

The overlap is the view's own frame against the keyboard's top edge, so a view that does not reach the bottom of the screen moves only as far as it must. The frame is measured in the window, which is what the keyboard reports its position in, so a view on a screen under a navigation bar needs no allowance for the bar. React Native measures in the parent's coordinates instead, and there a header's height has to go in keyboardVerticalOffset; here it would be counted twice. Use keyboardVerticalOffset only for extra room you want left above the keyboard.

On Android, when React Native's edge-to-edge flag is off (Expo Go leaves it off), the window React Native measures in starts below the status bar, while the keyboard's position counts from the top of the screen. The view takes the bar's height off the keyboard's position then, from Keyboard.windowTop, which is zero when the flag is on, so the status bar needs no keyboardVerticalOffset either.

The overlap never exceeds the view's own height, so a view the keyboard covers entirely is padded or shrunk to nothing rather than past it.

A body that should shrink and a bar that should sit on the keyboard both go inside:

<keyboard-avoiding-view class="flex-1">
  <scroll-view class="flex-1"><!-- the body --></scroll-view>
  <view class="flex-row px-5 py-2"><text>{{ wordCount() }} words</text></view>
</keyboard-avoiding-view>

A bar that rides the keyboard

A composer, a comment box or a row of formatting keys that should sit on the keyboard belongs in a <keyboard-dock> rather than at the bottom of a <keyboard-avoiding-view>. While a finger drags the keyboard down to dismiss it (keyboardDismissMode="interactive"), iOS reports no frame changes until the finger lets go, so anything moved by keyboard events - this view included - stays where it was and a gap opens under it.

On iOS, with react-native-keyboard-controller installed and provideKeyboardController() in the app's providers, the dock's bar is moved by the keyboard's height on the native side, every frame: as the keyboard rises and falls, and through an interactive dismissal. The drag takes the keyboard from the top of the bar, as Messages does, and a short drag springs it back. [keyboardLift] moves the content above with it:

import { provideKeyboardController } from '@ng-native/components';

export const appConfig = { providers: [provideKeyboardController() /* ... */] };
<view class="flex-1">
  <view class="flex-1 overflow-hidden">
    <view class="flex-1" [keyboardLift]="dock">
      <virtual-list
        class="flex-1"
        [inverted]="true"
        keyboardDismissMode="interactive"
        [items]="newestFirst()"
      >
        <!-- rows -->
      </virtual-list>
    </view>
  </view>
  <keyboard-dock #dock backgroundColor="#f4f4f7">
    <view class="flex-row p-2"><text-input class="flex-1" [multiline]="true" /></view>
  </keyboard-dock>
</view>

An inverted list draws its first item at the bottom, against the dock, so newestFirst is the transcript reversed: computed(() => this.messages().toReversed()).

The lifted view sits inside one that clips, so what rises past its top is hidden rather than drawn over the screen above it.

On Android, provideNativeKeyboard() from @ng-native/expo/keyboard moves the bar the same way, on the native side, every frame, and [keyboardLift] with it. It moves the bar by how far the keyboard covers the dock on screen, so a dock that is not at the bottom of the window rises only as far as it must, and one in a form sheet that react-native-screens raises with the keyboard is not lifted twice. A dock in a <modal> follows the keyboard of the modal's own window. Android does not use react-native-keyboard-controller even when it is installed: its view adds the navigation bar as margins around the whole app's content, which an app drawn edge to edge does not want.

import { provideKeyboardController } from '@ng-native/components';
import { provideNativeKeyboard } from '@ng-native/expo/keyboard';

export const appConfig = {
  providers: [provideKeyboardController(), provideNativeKeyboard() /* ... */],
};

Without either, the dock sits in flow, padded by as much of the keyboard as covers it, which it learns as the keyboard starts to move. On Android the bar jumps into place then, rather than moving with the keyboard.

covered() is how much of the bottom of the screen the keyboard covers beyond the dock's own place, for content that keeps clear of it with padding or an inset rather than moving with it. It is updated once the keyboard has moved. backgroundColor fills the bar and the inset under it.

While another screen is pushed over the dock's own or presented from it, the dock lets the keyboard go.

keyboard-dockcomponent@ng-native/components
import { KeyboardDock } from '@ng-native/components';and add KeyboardDock to the component's imports

A bar that sits on the keyboard: a chat's composer, a comment box, a row of formatting keys. At the bottom of its parent, clear of the home indicator or navigation bar, while the keyboard is down; on top of it while it is up.

Inputs

backgroundColor
string

The bar's background, under the inset it sits above as well as the bar itself.

Signals

covered
unknown

liftRange
ScrollRange

The bar, and anything keyboardLift moves, rises by the keyboard less the inset under it.

Methods

lift(view: HostNode, shift: number | null = null): ScrollDrive | null

Move a view with the keyboard on the native side, as the bar is moved: content above the bar that should rise with it. Null when native does not move the bar. See keyboardLift .

Your own style

Style the view as you would any other, with class or [style]. While the keyboard is up, the adjustment has the last word on the properties it moves, as it does in React Native: paddingBottom in padding mode, height and flex: 0 in height mode, and the inner view's bottom in position mode. So [style]="{flex: 1}" still shrinks in height mode, and every other property you wrote applies throughout. When the keyboard goes, your own values come back.

enabled (default true) stops avoiding without removing the view. With behavior="position", contentContainerStyle styles the inner view that gets shifted, and contentContainerClass gives it classes, matched as if the view were written in your own template, as on <scroll-view>.

Animation

The adjustment itself animates with the keyboard's own motion, the way React Native's own KeyboardAvoidingView does: a keyboard event's duration and easing configure the next layout animation before the style change that moves this view, so the two run together rather than the padding, height or position jumping to its resting place in one step.

Android animates no layout alongside the keyboard unless an app turns React Native's layout animations on, and its keyboard events carry no duration, so there the view jumps to its resting place as the keyboard starts to move. With provideNativeKeyboard() from @ng-native/expo/keyboard it follows the keyboard frame by frame instead: each frame is a commit from JavaScript, so it trails the keyboard by a frame or two, where a <keyboard-dock> is moved on the native side and does not.

keyboard-avoiding-viewcomponent@ng-native/components
import { KeyboardAvoidingView } from '@ng-native/components';and add KeyboardAvoidingView to the component's imports

Moves its content clear of the keyboard.

Inputs

behavior
KeyboardAvoidingBehavior = 'padding'

How to make room: pad the bottom, shrink the height, or shift the content up. RN leaves this unset and does nothing; padding is the default here because that is what a form wants.

contentContainerStyle
Record<string, unknown>

Styles for the inner view that position moves.

contentContainerClass
string

Classes for the inner view that position moves, matched as if it were written in the template this view is. See ContentContainer .

enabled
boolean = true

Set to false to stop avoiding without removing the view.

keyboardVerticalOffset
number = 0

Extra room to leave above the keyboard, in points. RN's name, and in RN the place to put a header's height; here the view is measured in the window, so a navigation bar needs none.

Angular components as native iOS and Android views, built and shipped with Expo.

An alpha. MIT licensed. Sponsor its development.

An independent project, not affiliated with or endorsed by Google, the Angular team or Expo. Angular is a trademark of Google LLC.