# Keyboard and insets

> **Info:** This is release candidate documentation for **Stream Chat React Native SDK v10**. For the latest stable version, see the [latest version (v9)](https://getstream.io/chat/docs/sdk/react-native/).

`Channel` and `Thread` keep the composer above the keyboard by themselves, and the composer keeps clear of the home indicator. They need two numbers from your layout: how far below the top of the screen the chat starts, for the keyboard, and how much of the window sits above and below the chat, for the attachment picker. This page explains both, and what changes with safe areas and edge-to-edge on Android.

## How the chat avoids the keyboard

`Channel` and `Thread` render their children inside `KeyboardCompatibleView`, a view adapted from React Native's `KeyboardAvoidingView`. It wraps the message list, the composer and the attachment picker. When the keyboard opens, it measures how much of the keyboard overlaps the view, and adds that much bottom padding, so the list shrinks and the composer rides on top of the keyboard. It listens to `keyboardWillShow` and `keyboardWillHide` on iOS, and to `keyboardDidShow` and `keyboardDidHide` on Android. While the app is in the background, it stops listening.

The view compares its own frame, which is relative to its parent, with the keyboard's position on the screen. `keyboardVerticalOffset` closes the gap between the two: it's the distance from the top of the screen to the top of the view that contains `Channel`.

## Setting keyboardVerticalOffset

Set `keyboardVerticalOffset` on every `Channel` and `Thread`:

- **Below a native navigation header**, such as the React Navigation native stack's or Expo Router's, the screen's views start below the header. Pass the header's height, from `useHeaderHeight()`.
- **With no header, or a header you render in JavaScript**, the screen's views start at the top of the screen. Pass `0`. This includes a header rendered as a child of `Channel`, and a `SafeAreaView` around `Channel`: the view measures the space they take as part of its own frame.

```tsx title="ChannelScreen.tsx"
import { useHeaderHeight } from "@react-navigation/elements";
import type { Channel as StreamChannel } from "stream-chat";
import {
  Channel,
  MessageComposer,
  MessageList,
} from "stream-chat-react-native";

export const ChannelScreen = ({ channel }: { channel: StreamChannel }) => {
  const headerHeight = useHeaderHeight();

  return (
    <Channel
      channel={channel}
      keyboardVerticalOffset={headerHeight}
      topInset={headerHeight}
    >
      <MessageList />
      <MessageComposer />
    </Channel>
  );
};
```

Use the same value on iOS and Android, and on the `Thread` of the same layout. When `keyboardVerticalOffset` isn't set, `KeyboardCompatibleView` uses its own default: `86.5` on iOS and `-300` on Android. Neither matches a layout with no header, so pass `0` there rather than leaving it out.

A tab bar below the chat doesn't change the offset, which only depends on what is above the chat.

## Android and edge-to-edge

On Android, what `KeyboardCompatibleView` does depends on whether the app draws edge-to-edge, which it reads from React Native's `DeviceInfo` constants:

- **An app that isn't edge-to-edge**, where the system bars take space from the window, relies on the system to resize the window when the keyboard opens (`android:windowSoftInputMode="adjustResize"`, as the React Native template sets it). The view adds no padding, so it doesn't add to the system's resize.
- **An edge-to-edge app** draws behind the system bars, and the window doesn't resize for the keyboard. The view adds the padding itself, as on iOS. Edge-to-edge is the default for apps that target Android 15 (API level 35).

On Android, an overlap no taller than the status bar counts as no overlap.

The rest of the SDK reads the system bars through the safe area insets, which cover them in an edge-to-edge app. The message menu keeps within the top and bottom insets. When an Android device reports a bottom inset of 0, it keeps 60 points clear at the bottom instead.

## Changing the keyboard view

`Channel` and `Thread` take these props for `KeyboardCompatibleView`:

| Prop                                  | Type                                  | Default                    | What it does                                                                                                        |
| ------------------------------------- | ------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `keyboardVerticalOffset`              | `number`                              | `86.5` iOS, `-300` Android | The distance from the top of the screen to the top of the view that contains `Channel`.                             |
| `keyboardBehavior`                    | `"padding" \| "position" \| "height"` | `"padding"`                | How the view makes room: bottom padding, moving its content up, or reducing its height.                             |
| `disableKeyboardCompatibleView`       | `boolean`                             | `false`                    | Leaves the view in place but stops it from reacting to the keyboard, for a screen that handles the keyboard itself. |
| `additionalKeyboardAvoidingViewProps` | `Partial<KeyboardAvoidingViewProps>`  |                            | More props for the view, such as `style`. They're applied last, so they override the three above.                   |

With `disableKeyboardCompatibleView`, something else has to keep the composer above the keyboard, such as a `KeyboardAvoidingView` around the screen.

### Replacing the view

`KeyboardCompatibleView` is a slot of `WithComponents`, and `Channel` and `Thread` render whatever component fills it, with the props above. The SDK doesn't use [`react-native-keyboard-controller`](https://kirillzyusko.github.io/react-native-keyboard-controller/) itself. To move the chat with its keyboard animations, put its `KeyboardAvoidingView` in the slot:

```tsx title="App.tsx"
import type { PropsWithChildren } from "react";
import {
  KeyboardAvoidingView,
  KeyboardProvider,
} from "react-native-keyboard-controller";
import { WithComponents } from "stream-chat-react-native";

// Read once, when WithComponents mounts, so the object stays the same.
const overrides = { KeyboardCompatibleView: KeyboardAvoidingView };

export const KeyboardSetup = ({ children }: PropsWithChildren) => (
  <KeyboardProvider>
    <WithComponents overrides={overrides}>{children}</WithComponents>
  </KeyboardProvider>
);
```

`KeyboardSetup` goes near the root of the app, around `OverlayProvider`. `react-native-keyboard-controller` needs its `KeyboardProvider` above every view that uses it; see its installation guide. Its `KeyboardAvoidingView` takes the same `behavior`, `enabled` and `keyboardVerticalOffset` as React Native's, so the props on `Channel` keep their meaning. Test the replacement on both platforms, with the attachment picker open and closed.

Whichever view fills the slot, a message closes the keyboard with `Keyboard.dismiss()` when it's long-pressed, and when it's tapped while `dismissKeyboardOnMessageTouch` is `true`, the default.

## The keyboard in your own components

The default `KeyboardCompatibleView` provides `KeyboardContext` to everything inside `Channel` and `Thread`. Its `dismissKeyboard()` closes the keyboard and returns a promise that resolves once the keyboard has hidden, or returns nothing when the keyboard isn't open. Wait for it before opening something that needs the whole screen:

```tsx
import { Button } from "react-native";
import { useKeyboardContext } from "stream-chat-react-native";

export const OpenDetailsButton = ({ onOpen }: { onOpen: () => void }) => {
  const { dismissKeyboard } = useKeyboardContext();

  return (
    <Button
      onPress={async () => {
        await dismissKeyboard();
        onOpen();
      }}
      title="Details"
    />
  );
};
```

Outside `Channel` and `Thread`, or with a replacement in the `KeyboardCompatibleView` slot, `useKeyboardContext()` returns React Native's `Keyboard.dismiss`, which returns right away.

`useAfterKeyboardOpenCallback(callback)` works the other way. The function it returns focuses the composer's input and runs `callback` once the keyboard is open, right away if it's already open or if the input isn't mounted. The default Edit action uses it, so the composer is in place before the message fills it. Call it inside `Channel` or `Thread`, because it reads the input from `MessageInputContext`.

## The attachment picker

The attachment picker opens in a bottom sheet, rendered by `Channel` or `Thread` below their children, in place of the keyboard. The sheet is `attachmentPickerBottomSheetHeight` tall: 333 by default, or 72 with `disableAttachmentPicker`, when only the selection bar shows. While it's open, the composer moves up by the same height. Opening the keyboard closes the picker.

The sheet sizes itself against the whole window, so it has to know how much of the window is outside the chat. `Channel` and `Thread` take two insets for this, both `0` by default:

- **`topInset`** is the space above the chat that isn't part of its views. Below a native navigation header, pass the header's height, the same value as `keyboardVerticalOffset`. Without it, the sheet measures from the top of the window, header included, and can't open to its full height. With no header, or a header rendered in JavaScript, leave it at `0`: the header is already part of the chat's layout, and the sheet would open too low.
- **`bottomInset`** is the space the sheet keeps free at the bottom. While the picker is open, the composer moves up by the sheet's height minus `bottomInset`. The composer already keeps clear of the bottom safe area by itself, so most screens leave it at `0`.

Both can change after `Channel` mounts, for example when the header hides. Pass the new value as a prop. `useAttachmentPickerContext()` reads the current values, and the picker's other props are on [Attachment picker](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message-composer/attachment-picker/).

## Safe areas

The SDK reads safe area insets from `react-native-safe-area-context`, so the app needs a `SafeAreaProvider` at its root. A navigation library usually renders one, and the [Quick start](https://getstream.io/chat/docs/sdk/react-native/v10/getting-started/quick-start/) adds one too.

The composer keeps clear of the bottom of the screen by itself. While the keyboard and the attachment picker are closed, its bottom padding is the bottom safe area inset, for the home indicator or the navigation bar, and 16 points when that inset is 0. While either is open, the padding is 16 points. So don't pad the bottom of the chat again:

- Leave out the bottom edge of a `SafeAreaView` around `Channel`. The `SampleApp` uses `edges={["left", "right"]}` below its header.
- Don't wrap `MessageComposer` in a `SafeAreaView` of its own.

With no navigation header, the top of the chat needs the top inset. A `SafeAreaView` with `edges={["top", "left", "right"]}` around `Channel` provides it, and `keyboardVerticalOffset` stays `0`:

```tsx title="FullScreenChannel.tsx"
import { StyleSheet } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import type { Channel as StreamChannel } from "stream-chat";
import {
  Channel,
  MessageComposer,
  MessageList,
} from "stream-chat-react-native";

export const FullScreenChannel = ({ channel }: { channel: StreamChannel }) => (
  <SafeAreaView edges={["top", "left", "right"]} style={styles.container}>
    <Channel channel={channel} keyboardVerticalOffset={0}>
      <MessageList />
      <MessageComposer />
    </Channel>
  </SafeAreaView>
);

const styles = StyleSheet.create({
  container: { flex: 1 },
});
```

The image gallery and the bottom sheets the SDK opens, such as the message's reactions, read the insets themselves.

## Used in

- [Navigation](https://getstream.io/chat/docs/sdk/react-native/v10/ui/layout/navigation/)
- [Channel UI](https://getstream.io/chat/docs/sdk/react-native/v10/ui/components/channel-ui/)
- [Message composer UI](https://getstream.io/chat/docs/sdk/react-native/v10/ui/components/message-composer-ui/)
- [Attachments and uploads](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/attachments-and-uploads/)

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v10/ui/layout/keyboard-and-insets/](https://getstream.io/chat/docs/sdk/react-native/v10/ui/layout/keyboard-and-insets/).