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}
    />
  );
}

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 and is likely to replace @legendapp/list/keyboard soon.

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

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.

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