React Native

Keyboard & Animated

These integrations are React Native only. On web, use standard DOM animation libraries or CSS transitions.

Reanimated

The Reanimated version of AnimatedLegendList supports animated props with Reanimated. Note that using Animated.createAnimatedComponent will not work as it needs more boilerplate, so you should use this instead.

Under the hood, these integrations use Reanimated.ScrollView.

Reanimated 4 sticky headers

In Reanimated 4, sticky headers can have performance problems. See Flickering/jittering while scrolling.

import { useEffect } from "react";
import { AnimatedLegendList } from "@legendapp/list/reanimated";
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated";

export function ReanimatedExample() {
  const scale = useSharedValue(0.8);

  useEffect(() => {
    scale.value = withSpring(1);
  }, []);

  return (
    <AnimatedLegendList
      data={data}
      renderItem={renderItem}
      style={useAnimatedStyle(() => ({
        transform: [{ scale: scale.value }]
      }))}
    />
  );
}

itemLayoutAnimation

Use itemLayoutAnimation to apply a Reanimated layout transition to list item containers.

import { AnimatedLegendList } from "@legendapp/list/reanimated";
import { LinearTransition } from "react-native-reanimated";

export function ReanimatedLayoutTransitionExample() {
  return (
    <AnimatedLegendList
      data={data}
      itemLayoutAnimation={LinearTransition.duration(280)}
      keyExtractor={(item) => item.id}
      renderItem={renderItem}
    />
  );
}

sharedValues

Use sharedValues when you want LegendList to keep external Reanimated shared values in sync with list state.

import { useSharedValue } from "react-native-reanimated";
import { AnimatedLegendList } from "@legendapp/list/reanimated";

export function ReanimatedSharedValuesExample() {
  const scrollOffset = useSharedValue(0);
  const isAtEnd = useSharedValue(false);
  const isNearEnd = useSharedValue(false);

  return (
    <AnimatedLegendList
      data={data}
      keyExtractor={(item) => item.id}
      renderItem={renderItem}
      sharedValues={{
        scrollOffset,
        isAtEnd,
        isNearEnd,
      }}
    />
  );
}

Supported shared values:

  • activeStickyIndex
  • isAtEnd
  • isAtStart
  • isNearEnd
  • isNearStart
  • isWithinMaintainScrollAtEndThreshold
  • scrollOffset

Animated

AnimatedLegendList supports animated props with React Native's Animated.

import { useEffect, useRef } from "react";
import { Animated } from "react-native";
import { AnimatedLegendList } from "@legendapp/list/animated";

export function AnimatedExample() {
  const animated = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.timing(animated, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
    }).start();
  }, []);

  return (
    <AnimatedLegendList
      data={data}
      renderItem={renderItem}
      style={{ opacity: animated }}
    />
  );
}

Note that this is just a wrapper around the normal createAnimatedComponent so you can use that if you prefer.

const AnimatedLegendList = Animated.createAnimatedComponent(LegendList);

KeyboardAvoidingLegendList

Use KeyboardAvoidingLegendList from @legendapp/list/keyboard for smooth keyboard-aware scrolling and inset behavior.

An experimental entrypoint is also available at @legendapp/list/keyboard-test. It currently uses KeyboardChatScrollView.

import { KeyboardAvoidingLegendList } from "@legendapp/list/keyboard-test";

This integration depends on react-native-reanimated and react-native-keyboard-controller.

npm install react-native-keyboard-controller react-native-reanimated

Integration guidance

Do not wrap KeyboardAvoidingLegendList inside another KeyboardAvoidingView. Let the list manage keyboard-aware behavior, and adjacent UI (like composers/inputs) should handle their own keyboard avoiding (for example with KeyboardStickyView).

Advanced customization

If your app needs more advanced keyboard-avoidance behavior, use KeyboardAvoidingLegendList as a starting point and adapt it for your scenario. See the source: src/integrations/keyboard.tsx.

KeyboardChatLegendList

Use KeyboardChatLegendList from @legendapp/list/keyboard-chat if you want a lower-level KeyboardChatScrollView integration with explicit anchoredEndSpace control.

import { KeyboardChatLegendList } from "@legendapp/list/keyboard-chat";

This is useful for chat apps where you want to scroll the user message to the top on send.

<KeyboardChatLegendList
  data={messages}
  keyExtractor={(item) => item.id}
  renderItem={ChatMessage}
  anchoredEndSpace={{ anchorIndex: messages.length - 1, anchorOffset: 16 }}
/>

This integration depends on react-native-reanimated and react-native-keyboard-controller.

npm install react-native-keyboard-controller react-native-reanimated

Chat Example

For chat screens, KeyboardChatLegendList works with a few chat-specific pieces:

  • KeyboardStickyView keeps the composer attached to the keyboard while the list fills the remaining space.
  • useKeyboardScrollToEnd coordinates the imperative scroll with keyboard dismissal after a message is sent.
  • anchoredEndSpace reserves space after the newly sent message so it can land near the top of the visible area instead of being hidden behind the composer.
  • initialScrollAtEnd starts the conversation at the latest message, while maintainVisibleContentPosition keeps the viewport stable as new rows arrive.
import { useRef, useState } from "react";
import { Button, TextInput, View } from "react-native";
import { KeyboardGestureArea, KeyboardProvider, KeyboardStickyView } from "react-native-keyboard-controller";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { KeyboardChatLegendList, useKeyboardScrollToEnd } from "@legendapp/list/keyboard-chat";
import type { LegendListRef } from "@legendapp/list/react-native";

export function KeyboardChatExample() {
  const listRef = useRef<LegendListRef>(null);
  const [messages, setMessages] = useState(defaultChatMessages);
  const [anchorIndex, setAnchorIndex] = useState<number | undefined>(undefined);
  const [inputText, setInputText] = useState("");
  const insets = useSafeAreaInsets();
  const { scrollMessageToEnd } = useKeyboardScrollToEnd({ listRef });

  const sendMessage = () => {
    const text = inputText || "Empty message";
    if (text.trim()) {
      // Anchor the list at the message being sent so it can scroll above the composer.
      setAnchorIndex(messages.length);
      setMessages((messagesNew) => [
        ...messagesNew,
        { id: String(idCounter++), sender: "user", text: text, timeStamp: Date.now() },
      ]);
      setInputText("");

      // Wait for React to commit the new row before measuring and scrolling to the end.
      requestAnimationFrame(() => {
        scrollMessageToEnd({ animated: true, closeKeyboard: true });
      });
    }
  };

  return (
    <KeyboardProvider>
      <View style={[styles.container, { paddingBottom: insets.bottom, paddingTop: insets.top }]}>
        <KeyboardGestureArea interpolator="ios" offset={60} style={styles.container}>
          <KeyboardChatLegendList
            alignItemsAtEnd
            anchoredEndSpace={anchorIndex !== undefined ? { anchorIndex } : undefined}
            contentContainerStyle={styles.contentContainer}
            data={messages}
            estimatedItemSize={80}
            initialScrollAtEnd
            keyExtractor={(item) => item.id}
            keyboardDismissMode="interactive"
            maintainScrollAtEnd
            maintainVisibleContentPosition
            offset={insets.bottom}
            ref={listRef}
            renderItem={ChatMessage}
            style={styles.list}
          />
        </KeyboardGestureArea>
        <KeyboardStickyView offset={{ closed: 0, opened: insets.bottom }}>
          <View style={styles.inputContainer}>
            <TextInput
              onChangeText={setInputText}
              placeholder="Type a message"
              style={styles.input}
              value={inputText}
            />
            <Button onPress={sendMessage} title="Send" />
          </View>
        </KeyboardStickyView>
      </View>
    </KeyboardProvider>
  );
}

On this page