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:
activeStickyIndexisAtEndisAtStartisNearEndisNearStartisWithinMaintainScrollAtEndThresholdscrollOffset
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-reanimatedIntegration 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-reanimatedChat Example
For chat screens, KeyboardChatLegendList works with a few chat-specific pieces:
KeyboardStickyViewkeeps the composer attached to the keyboard while the list fills the remaining space.useKeyboardScrollToEndcoordinates the imperative scroll with keyboard dismissal after a message is sent.anchoredEndSpacereserves space after the newly sent message so it can land near the top of the visible area instead of being hidden behind the composer.initialScrollAtEndstarts the conversation at the latest message, whilemaintainVisibleContentPositionkeeps 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>
);
}