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

import { useState } from "react";
import { Button, TextInput, View } from "react-native";
import { KeyboardGestureArea, KeyboardProvider, KeyboardStickyView } from "react-native-keyboard-controller";
import { useAnimatedScrollHandler } from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { KeyboardAvoidingLegendList } from "@legendapp/list/keyboard";

export function KeyboardAvoidingExample() {
  const [messages, setMessages] = useState(defaultChatMessages);
  const [inputText, setInputText] = useState("");
  const insets = useSafeAreaInsets();

  const sendMessage = () => {
    const text = inputText || "Empty message";
    if (text.trim()) {
      setMessages((messagesNew) => [
        ...messagesNew,
        { id: String(idCounter++), sender: "user", text: text, timeStamp: Date.now() },
      ]);
      setInputText("");
    }
  };

  const handleScroll = useAnimatedScrollHandler({
    onScroll: (_event) => {},
  });

  return (
    <KeyboardProvider>
      <View style={[styles.container, { paddingBottom: insets.bottom, paddingTop: insets.top }]}>
        <KeyboardGestureArea interpolator="ios" offset={60} style={styles.container}>
          <KeyboardAvoidingLegendList
            alignItemsAtEnd
            contentContainerStyle={styles.contentContainer}
            data={messages}
            estimatedItemSize={80}
            initialScrollAtEnd
            keyExtractor={(item) => item.id}
            maintainScrollAtEnd
            maintainVisibleContentPosition
            onScroll={handleScroll}
            renderItem={ChatMessage}
            safeAreaInsetBottom={insets.bottom}
            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