# API Reference

API Reference — Legend List documentation.

Source: https://legend.so/list/v3/api/

Legend List v3 documentation. Current documentation version.

This page documents LegendList APIs for both React Native and React (Web). Features marked **New in 3.7** require 3.7.0 (unreleased).

Props apply to both React Native and Web unless otherwise noted. Platform-specific notes are called out inline.

## [Imports and entrypoints](https://legend.so/list/v3/api/#imports-and-entrypoints)

```ts
// Choose one platform-specific LegendList import
import { LegendList } from "@legendapp/list/react-native";
import { LegendList } from "@legendapp/list/react";

// Optional entrypoints
import { SectionList } from "@legendapp/list/section-list";
import { AnimatedLegendList } from "@legendapp/list/animated";
import { AnimatedLegendList as ReanimatedLegendList } from "@legendapp/list/reanimated";
import {
  KeyboardAwareLegendList,
  useKeyboardChatComposerInset,
  useKeyboardScrollToEnd,
} from "@legendapp/list/keyboard";
import { KeyboardAvoidingLegendList } from "@legendapp/list/keyboard-legacy";
```

See [Keyboard & Animated](https://legend.so/list/v3/react-native/keyboard-and-animated) for Reanimated-only props such as `sharedValues` and `itemLayoutAnimation`.

## [Required Props](https://legend.so/list/v3/api/#required-props)

* * *

LegendList supports two render modes:

-   Data mode: `data` + `renderItem`
-   Children mode: `children`

When using one mode, the other mode's props should not be provided.

### [children](https://legend.so/list/v3/api/#children)

```ts
children: ReactNode;
```

Render list items directly as children in children mode (instead of `data`/`renderItem`).

### [data](https://legend.so/list/v3/api/#data)

```ts
data: readonly ItemT[];
```

An array of the items to render in data mode. This can also be an array of keys if you want to get the item by key in [renderItem](https://legend.so/list/v3/api/#renderitem).

### [renderItem](https://legend.so/list/v3/api/#renderitem)

```ts
renderItem: (props: { item: ItemT; index: number; extraData: any; type: string | undefined; data: readonly ItemT[] }) => ReactNode;
```

Takes an item from data and renders it into the list in data mode. The `type` parameter comes from `getItemType`, or is an empty string when no type is resolved.

`renderItem` is called as a render callback. If your row uses hooks, return a component from the callback rather than passing the component function directly:

```tsx
const Row = ({ item }: { item: ItemT }) => {
  const value = useRowValue(item.id);
  return <Text>{value}</Text>;
};

<LegendList
  data={items}
  renderItem={({ item }) => <Row item={item} />}
/>
```

See [React Native Docs](https://reactnative.dev/docs/flatlist#renderItem).

  

## [Recommended Props](https://legend.so/list/v3/api/#recommended-props)

* * *

### [keyExtractor](https://legend.so/list/v3/api/#keyextractor)

```ts
keyExtractor?: (item: ItemT, index: number) => string;
```

Strongly recommended. The `keyExtractor` prop lets Legend List save item layouts by key, so when the `data` array changes it can reuse previous layout information and update only the changed items.

Return a stable unique key for each logical item. Reusing a key for different items, or using the item index in a list that can reorder or prepend items, will attach cached measurements and recycled state to the wrong row. Index keys are only reasonable for static or append-only lists where earlier item positions never change. See [Use key extractor](https://legend.so/list/v3/performance#use-keyextractor).

If LegendList detects duplicate keys in development, it will log an error.

### [recycleItems (React Native)](https://legend.so/list/v3/api/#recycleitems-react-native)

```ts
recycleItems?: boolean; // default: false
```

This will reuse the component rendered by your `renderItem` function. This can be a big performance improvement, but if your list items have internal state there is potential for state to carry over when a component is recycled for a different item. See [Performance](https://legend.so/list/v3/performance#recycling-list-items).

This is mostly useful for React Native to reuse native views - it has a neglibible effect on web.

  

## [Optional Props](https://legend.so/list/v3/api/#optional-props)

* * *

### [alignItemsAtEnd](https://legend.so/list/v3/api/#alignitemsatend)

```ts
alignItemsAtEnd?: boolean; // default: false
```

Aligns to the end of the screen. If there's only a few items, Legend List will add padding to the top to align them to the bottom. See [Chat interfaces](https://legend.so/list/v3/guides#chat-interfaces) for more.

### [alwaysRender](https://legend.so/list/v3/api/#alwaysrender)

```ts
alwaysRender?: { top?: number; bottom?: number; indices?: number[]; keys?: string[] };
```

Keeps selected items mounted even when they scroll out of view. Use this for pinned items or sentinels. `keys` requires a stable `keyExtractor`.

### [anchoredEndSpace](https://legend.so/list/v3/api/#anchoredendspace)

```ts
anchoredEndSpace?: {
  anchorIndex: number;
  anchorOffset?: number;
  anchorMaxSize?: number;
  onReady?: (info: {
    anchorIndex: number | undefined;
    anchorKey: string | undefined;
    size: number;
  }) => void;
  onSizeChanged?: (size: number) => void;
};
```

Keeps a chosen item visually anchored to the start by adding trailing space when the content below that item underflows.

-   `anchorIndex`: required index of the item to keep anchored.
-   `anchorOffset`: subtracts pixels from the computed blank space. Useful when the anchored row should stop short of the edge.
-   `anchorMaxSize`: caps the anchor item's size contribution when calculating trailing space.
-   `onReady`: called when LegendList has authoritative anchored-tail sizing for the current anchor. Use this when an integration needs to wait until the inserted tail space is known before running follow-up scroll work.
-   `onSizeChanged`: called whenever the inserted blank space changes.

Platform notes:

-   Web: available on `LegendList` from `@legendapp/list/react`.
-   React Native: available on `LegendList` from `@legendapp/list/react-native`. Use `KeyboardAwareLegendList` for keyboard-driven insets.

`anchoredEndSpace` supports single-column lists (`numColumns={1}`).

### [columnWrapperStyle](https://legend.so/list/v3/api/#columnwrapperstyle)

```ts
columnWrapperStyle?: { gap?: number; rowGap?: number; columnGap?: number };
```

Sets spacing between rows and columns.

### [contentContainerClassName](https://legend.so/list/v3/api/#contentcontainerclassname)

```ts
contentContainerClassName?: string;
```

Web only. Adds a `className` to the inner content div inside the scroll container. Use this when your app styles the content container with CSS classes instead of inline styles.

Gap classes

`gap-*`, `gap-x-*`, and `gap-y-*` classes in `contentContainerClassName` are not used for LegendList item spacing because items are positioned virtually. Use `contentContainerStyle={{ gap: 16, padding: 16 }}` or `columnWrapperStyle` instead.

### [contentContainerStyle](https://legend.so/list/v3/api/#contentcontainerstyle)

```ts
contentContainerStyle?: StyleProp<ViewStyle>;
```

Style applied to the underlying ScrollView's content container. On web, this maps to the inner content div’s CSS styles.

### [contentInset](https://legend.so/list/v3/api/#contentinset)

```ts
contentInset?: { top: number; left: number; bottom: number; right: number };
```

React Native only. Sets ScrollView content insets. On web, prefer padding via `contentContainerStyle` or `style`.

### [contentInsetEndAdjustment](https://legend.so/list/v3/api/#contentinsetendadjustment)

```ts
contentInsetEndAdjustment?: number;
```

Web only on `LegendList` from `@legendapp/list/react`. Adjusts the effective end inset without replacing the base `contentInset`.

LegendList also renders the adjustment as real trailing DOM space, so browser scroll range, `scrollToEnd`, and end-pinned behavior stay aligned when a floating composer or overlay grows and shrinks.

For React Native keyboard-aware lists, pass a Reanimated shared value to `KeyboardAwareLegendList` instead:

```tsx
const { contentInsetEndAdjustment, onComposerLayout } =
  useKeyboardChatComposerInset(listRef, composerRef);

<KeyboardAwareLegendList
  contentInsetEndAdjustment={contentInsetEndAdjustment}
  ref={listRef}
  {...props}
/>
```

Use this for floating composers, input bars, or other UI that visually covers the end of the list while remaining outside normal list content flow.

### [dataKey](https://legend.so/list/v3/api/#datakey)

```ts
dataKey?: Key;
```

Identity token for the logical dataset. Change it when replacing the list with a different dataset so initial positioning can restart. Use `dataVersion` for updates within the same dataset.

### [dataVersion](https://legend.so/list/v3/api/#dataversion)

```ts
dataVersion?: Key;
```

Version token that forces the list to treat data as updated even when the array reference is stable. Increment this when mutating `data` in place.

### [drawDistance](https://legend.so/list/v3/api/#drawdistance)

```ts
drawDistance?: number;
```

The `drawDistance` (defaults to `250`) is the buffer size in pixels above and below the viewport that will be rendered in advance. See [Performance](https://legend.so/list/v3/performance#set-drawdistance-prop) for more.

### [estimatedHeaderSize](https://legend.so/list/v3/api/#estimatedheadersize)

```ts
estimatedHeaderSize?: number;
```

Estimated height of `ListHeaderComponent` before it is measured.

Use this when the expected header height is known before layout and the header appears above the first visible items. It lets LegendList allocate only the rows that are actually visible below the header on the initial frame, instead of rendering a full viewport of rows that may be hidden behind the header.

The measured header size replaces this estimate after layout.

### [estimatedItemSize](https://legend.so/list/v3/api/#estimateditemsize)

```ts
estimatedItemSize?: number;
```

Optional first-render allocation hint. Legend List works well without this prop, and after rows are measured it uses measured item sizes and averages instead.

In v3, `estimatedItemSize` mostly affects how many item containers are allocated before measurement. The default estimate is `100px`, so you usually do not need to set it unless your rows are significantly larger or smaller than that, or you need better initial offsets for a far `initialScrollIndex` / `snapToIndices` target.

### [estimatedListSize](https://legend.so/list/v3/api/#estimatedlistsize)

```ts
estimatedListSize?: { height: number; width: number };
```

Estimated size of the list viewport used as a first-render hint before actual layout is measured.

### [extraData](https://legend.so/list/v3/api/#extradata)

```ts
extraData?: any;
```

Avoid this when possible. `extraData` is a whole-list invalidation escape hatch, not the recommended way to pass changing item state.

Changing `extraData` causes Legend List to re-render all items because the value is passed through every `renderItem` call. That can be expensive for large lists or frequently changing state.

Prefer external state that each item subscribes to directly, for example React context, a state library selector, or another item-scoped subscription. Use `extraData` only when you intentionally need every rendered item to re-evaluate from the same changed value.

**New in 3.7:** When `getFixedItemSize` is set, changing `extraData` also invalidates all cached fixed sizes and recalculates item positions. Pass the external value here if it changes the fixed sizes returned by your callback.

See [React Native Docs](https://reactnative.dev/docs/flatlist#extraData).

### [experimental\_adaptiveRender](https://legend.so/list/v3/api/#experimental_adaptiverender)

```ts
type AdaptiveRender = "normal" | "light";

experimental_adaptiveRender?: {
  initialMode?: AdaptiveRender; // default: "normal"
  enterVelocity?: number; // default: 3 native, 6 web
  exitVelocity?: number; // default: 1 native, 3 web
  exitDelay?: number; // default: 250
  onChange?: (mode: AdaptiveRender, reason: "initial" | "ready" | "scroll") => void;
};
```

Enables an adaptive render signal for expensive item content. This is a performance optimization for lists whose rows do significant work while rendering, such as charts, media, syntax highlighting, rich previews, embeds, or expensive formatting.

When scroll velocity crosses `enterVelocity`, LegendList switches the signal to `"light"` so rows can render a cheaper version while the list is moving quickly. After velocity drops below `exitVelocity` for `exitDelay` milliseconds, it returns to `"normal"`. This reduces JS/render work during fast scrolls and lets full content come back when the user slows down or stops.

Use `initialMode` when rows should start in `"light"` mode before the list is ready, for example when you prefer a very cheap first pass before expensive media or charts render.

The `onChange` reason identifies initialization/reset (`"initial"`), list readiness (`"ready"`), or scrolling (`"scroll"`).

Good uses for light mode include:

-   Rendering unformatted text instead of expensive formatted text, markdown, or syntax highlighting.
-   Rendering skeleton boxes instead of images, videos, charts, or rich previews.
-   Skipping gesture detectors, menus, modal setup, or other interactive wrappers that are only needed when the row is stable in view.

There are two main ways to use adaptive rendering:

1.  Inside rendered items, read the current mode with [useAdaptiveRender](https://legend.so/list/v3/api/#useadaptiverender) or subscribe to changes with [useAdaptiveRenderChange](https://legend.so/list/v3/api/#useadaptiverenderchange). This is the simplest option when each row can choose its own light/full render.
2.  Use `experimental_adaptiveRender.onChange` to write the mode into global or external state, then let expensive child components subscribe to that state. This is useful when adaptive mode needs to affect components deeper in the row tree or components that are shared outside the direct list item boundary.

```tsx
<LegendList
  data={items}
  renderItem={({ item }) => <FeedRow item={item} />}
  experimental_adaptiveRender={{
    enterVelocity: 6,
    exitVelocity: 3,
    onChange: (mode) => adaptiveRenderStore.set(mode),
  }}
/>
```

Use adaptive rendering for the expensive part of a row, not as a replacement for basic row memoization or stable `renderItem` patterns. Simple text rows usually do not need it.

The light version should keep the same rendered size as the normal version. If switching modes changes an item's height or width, the list has to correct measured positions while scrolling, which can cause visible layout shifts.

### [experimental\_hideItemsUntilMeasured](https://legend.so/list/v3/api/#experimental_hideitemsuntilmeasured)

```ts
experimental_hideItemsUntilMeasured?: boolean; // default: false
```

On React Native's new architecture, keeps dynamic rows invisible until measured to avoid flashing at estimated positions. This adds render/native commit work. Rows with known fixed or measured sizes render immediately, and sticky headers are excluded.

### [getFixedItemSize](https://legend.so/list/v3/api/#getfixeditemsize)

```ts
getFixedItemSize?: (item: ItemT, index: number, itemType?: string) => number | undefined;
```

For items with known fixed sizes, this enables optimal performance as it disables the overhead of measuring and updating item size. Return a number for fixed-size items or undefined for dynamic-size items.

**New in 3.7:** Changes to [extraData](https://legend.so/list/v3/api/#extradata) invalidate cached fixed sizes, so sizes can depend on that external value.

### [getItemType](https://legend.so/list/v3/api/#getitemtype)

```ts
getItemType?: (item: ItemT, index: number) => string;
```

Allows categorizing different item types for better performance optimization. Items with the same type can be recycled more efficiently.

### [horizontal](https://legend.so/list/v3/api/#horizontal)

```ts
horizontal?: boolean; // default: false
```

Renders items along the horizontal axis instead of the vertical axis.

### [initialScrollAtEnd](https://legend.so/list/v3/api/#initialscrollatend)

```ts
initialScrollAtEnd?: boolean; // default: false
```

When true, the list initializes scrolled to the last item. Overrides `initialScrollIndex` and `initialScrollOffset` when data is available.

This is designed for chat/feed screens and works with dynamic item measurement, async data arrival, and end-inset changes from floating composers. On iOS, LegendList waits for native initial-scroll confirmation before marking the initial scroll complete.

### [initialScrollIndex](https://legend.so/list/v3/api/#initialscrollindex)

```ts
initialScrollIndex?: number | {
  index: number;
  viewOffset?: number;
  viewPosition?: number;
  viewPositionFallback?: "start" | "end";
};
```

Start scrolled with this item at the top (or at the provided `viewPosition`). If item sizes are dynamic, the list will adjust after measurement using the default scroll‑stabilization behavior.

`viewPositionFallback` chooses start/end alignment only when the target item exceeds the available viewport. Omit it to preserve `viewPosition`.

For large lists, LegendList can seed the initial render near the target index instead of scanning from the beginning when it has enough sizing information. If data arrives after mount, the initial target is re-armed and applied when items are available.

### [initialScrollOffset](https://legend.so/list/v3/api/#initialscrolloffset)

```ts
initialScrollOffset?: number;
```

Start scrolled to this offset.

### [itemsAreEqual](https://legend.so/list/v3/api/#itemsareequal)

```ts
itemsAreEqual?: (itemPrevious: ItemT, item: ItemT, index: number, data: readonly ItemT[]) => boolean;
```

Optional equality comparator used during data changes to preserve known item sizes and reduce relayout work when items are logically unchanged.

### [ItemSeparatorComponent](https://legend.so/list/v3/api/#itemseparatorcomponent)

```ts
ItemSeparatorComponent?: React.ComponentType<{ leadingItem: ItemT }>
```

Rendered in between each item, but not at the top or bottom.

See [React Native Docs](https://reactnative.dev/docs/flatlist#itemseparatorcomponent).

### [ListEmptyComponent](https://legend.so/list/v3/api/#listemptycomponent)

```ts
ListEmptyComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;
```

Rendered when the list is empty.

See [React Native Docs](https://reactnative.dev/docs/flatlist#listemptycomponent).

### [ListFooterComponent](https://legend.so/list/v3/api/#listfootercomponent)

```ts
ListFooterComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;
```

Rendered at the bottom of all the items.

See [React Native Docs](https://reactnative.dev/docs/flatlist#listfootercomponent).

### [ListFooterComponentStyle](https://legend.so/list/v3/api/#listfootercomponentstyle)

```ts
ListFooterComponentStyle?: StyleProp<ViewStyle> | undefined;
```

Styling for internal View for `ListFooterComponent`.

See [React Native Docs](https://reactnative.dev/docs/flatlist#listfootercomponentstyle).

### [ListHeaderComponent](https://legend.so/list/v3/api/#listheadercomponent)

```ts
ListHeaderComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;
```

Rendered at the top of all the items.

See [React Native Docs](https://reactnative.dev/docs/flatlist#listheadercomponent).

### [ListHeaderComponentStyle](https://legend.so/list/v3/api/#listheadercomponentstyle)

```ts
ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
```

Styling for internal View for `ListHeaderComponent`.

See [React Native Docs](https://reactnative.dev/docs/flatlist#listheadercomponentstyle).

### [maintainScrollAtEnd](https://legend.so/list/v3/api/#maintainscrollatend)

```ts
maintainScrollAtEnd?: boolean | {
  animated?: boolean;
  on?: {
    dataAppend?: boolean;
    dataChange?: boolean;
    footerLayout?: boolean;
    itemLayout?: boolean;
    layout?: boolean;
  };
};
```

If enabled, LegendList keeps the view pinned to end when you are near the bottom.

-   `true`: enables end-maintenance for layout, footer-layout, item-layout, and data updates.
-   `animated`: whether the automatic scroll-to-end should animate. Defaults to `false`.
-   If `on` is omitted, the object form enables all triggers.
-   If `on` is provided, only the keys set to `true` are enabled.
-   `dataAppend`: **New in 3.7.** With a stable `keyExtractor`, follow additions at the end only when every existing item key remains in the same order at the start of the new data. Prepends, removals, reorders, and same-length updates do not trigger this option. Without `keyExtractor`, keys are indices, so any increase in data length, including a prepend, triggers following.
-   `dataChange`: follow all data changes. Keep it omitted or `false` for append-only following; setting it to `true` takes precedence over `dataAppend`.
-   `footerLayout`: opt into footer size changes when using an explicit `on` config. This is useful for chat typing indicators and dynamic footers that should keep an end-pinned list at the bottom.

See [Chat interfaces](https://legend.so/list/v3/guides#chat-interfaces) for more.

### [maintainScrollAtEndThreshold](https://legend.so/list/v3/api/#maintainscrollatendthreshold)

```ts
maintainScrollAtEndThreshold?: number;
```

This defines what percent of the screen counts as the bottom. Defaults to `0.1`.

**New in 3.7:** End thresholds count trailing content and keyboard insets once, including for `maintainScrollAtEnd`, `isAtEnd`, and `onEndReached`. With a trailing inset, these activate closer to the physical end instead of firing early by the inset amount.

See [Chat interfaces](https://legend.so/list/v3/guides#chat-interfaces) for more.

### [maintainVisibleContentPosition](https://legend.so/list/v3/api/#maintainvisiblecontentposition)

```ts
maintainVisibleContentPosition?: boolean | {
  data?: boolean;
  size?: boolean;
  shouldRestorePosition?: (item: ItemT, index: number, data: readonly ItemT[]) => boolean;
};
```

Controls how the list stabilizes scroll position when items above the viewport change.

-   `size` (default: true): stabilizes during size/layout changes while scrolling
-   `data` (default: false): anchors when the data array changes
-   `shouldRestorePosition`: return `false` to skip anchoring for specific items

Passing `true` enables both `size` and `data`. Passing `false` disables both.

React Native note: when either `size` or `data` stabilization is enabled, LegendList sets ScrollView’s [maintainVisibleContentPosition](https://reactnative.dev/docs/scrollview#maintainvisiblecontentposition) under the hood. Android requires React Native 0.72+ for that prop.

### [numColumns](https://legend.so/list/v3/api/#numcolumns)

```ts
numColumns?: number;
```

Multiple columns will zig-zag like a flexWrap layout. Rows will take the maximum height of their columns, so items should all be the same height - masonry layouts are not supported.

### [onEndReached](https://legend.so/list/v3/api/#onendreached)

```ts
onEndReached?: ((info: { distanceFromEnd: number }) => void) | null | undefined;
```

Called when the list enters the `onEndReachedThreshold` range near the bottom. Repeated callbacks are gated to avoid duplicate loads after data/layout changes. Moving beyond the range uses hysteresis before resetting; a new user scroll gesture can re-arm the callback.

**New in 3.7:** Row measurements and header/footer size changes recheck the threshold, so `onEndReached` can fire without scrolling when layout brings the edge into range.

### [onEndReachedThreshold](https://legend.so/list/v3/api/#onendreachedthreshold)

```ts
onEndReachedThreshold?: number | null | undefined;
```

The distance from the end as a percentage that the scroll should be from the end to trigger `onEndReached`. It is multiplied by screen size, so a value of 0.5 will trigger `onEndReached` when scrolling to half a screen from the end.

**New in 3.7:** Trailing content and keyboard insets are counted once when checking this threshold.

### [onFirstVisibleItemChanged](https://legend.so/list/v3/api/#onfirstvisibleitemchanged)

```ts
onFirstVisibleItemChanged?: (info: {
  index: number;
  item: ItemT;
  key: string;
}) => void;
```

Called when the first visible item changes. This is emitted from LegendList's core range calculation and is cheaper than full viewability tracking when you only need to follow the item at the top of the viewport, such as updating a section label, mini-map, or current date marker.

### [onItemSizeChanged](https://legend.so/list/v3/api/#onitemsizechanged)

```ts
onItemSizeChanged?: (info: {
        size: number;
        previous: number;
        index: number;
        itemKey: string;
        itemData: ItemT;
    }) => void;
```

Called whenever an item's rendered size changes. This can be used to inspect real row sizes, especially if you are deciding whether `estimatedItemSize` is worth setting for unusual item sizes.

### [onLoad](https://legend.so/list/v3/api/#onload)

```ts
onLoad?: (info: { elapsedTimeInMs: number }) => void;
```

Called after the list is ready to render. Useful for measuring first render readiness.

### [onReady](https://legend.so/list/v3/api/#onready)

```ts
onReady?: () => void;
```

Called after layout and initial scrolling complete, including a new placement cycle after a `dataKey` reset. Unlike `onLoad`, it can run more than once.

### [onMetricsChange](https://legend.so/list/v3/api/#onmetricschange)

```ts
onMetricsChange?: (metrics: { headerSize: number; footerSize: number }) => void;
```

Called when list layout metrics change (header or footer size updates).

### [onRefresh](https://legend.so/list/v3/api/#onrefresh)

```ts
onRefresh?: () => void;
```

React Native only. Called whenever a user pulls down to refresh. See [React Native Docs](https://reactnative.dev/docs/flatlist#onRefresh).

### [onScroll](https://legend.so/list/v3/api/#onscroll)

```ts
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
```

Called on scroll events with platform-specific scroll data.

### [onStartReached](https://legend.so/list/v3/api/#onstartreached)

```ts
onStartReached?: ((info: { distanceFromStart: number }) => void) | null | undefined;
```

Called when the list enters the `onStartReachedThreshold` range near the top. Repeated callbacks are gated to avoid duplicate loads after data/layout changes. Moving beyond the range uses hysteresis before resetting; a new user scroll gesture can re-arm the callback.

**New in 3.7:** Row measurements and header/footer size changes recheck the threshold, so `onStartReached` can fire without scrolling when layout brings the edge into range.

### [onStartReachedThreshold](https://legend.so/list/v3/api/#onstartreachedthreshold)

```ts
onStartReachedThreshold?: number | null | undefined;
```

The distance from the start as a percentage that the scroll should be from the start to trigger `onStartReached`. It is multiplied by screen size, so a value of 0.5 will trigger `onStartReached` when scrolling to half a screen from the start.

### [onStickyHeaderChange](https://legend.so/list/v3/api/#onstickyheaderchange)

```ts
onStickyHeaderChange?: (info: { index: number; item: any }) => void;
```

Called when the active sticky header changes.

### [onUserScroll](https://legend.so/list/v3/api/#onuserscroll)

**New in 3.7 · Experimental**

```ts
onUserScroll?: () => void;
```

Called once per accepted real user gesture, including a native drag and its momentum, web touch/wheel gesture, scroll key press, scrollbar input, or iOS status-bar tap. List-owned and list-ref scrolls, layout adjustments, and clamping do not call it. Use [onScroll](https://legend.so/list/v3/api/#onscroll) for individual scroll events.

Gesture detection is experimental: wheel grouping uses heuristics; native pointer/wheel-only input without a screen reader is not detected, and web scrollbar detection requires a measurable, non-overlay gutter. With a native screen reader enabled, otherwise unowned accessibility scrolling is inferred once per burst; assistive input during library animations can be missed. Before navigation that bypasses the list (such as native-ref commands or Reanimated `scrollTo`), call `cancelScroll()` on the JS thread to release end following. Following can resume if that navigation settles within `maintainScrollAtEndThreshold`.

### [onViewableItemsChanged](https://legend.so/list/v3/api/#onviewableitemschanged)

```ts
onViewableItemsChanged?: OnViewableItemsChanged<ItemT> | undefined;
```

Called when the viewability of rows changes, as defined by the `viewabilityConfig` prop.

The callback receives `viewableItems`, `changed`, and the visible/buffered index ranges: `start`, `end`, `startBuffered`, `endBuffered`.

See [React Native Docs](https://reactnative.dev/docs/flatlist#onviewableitemschanged).

### [overrideItemLayout](https://legend.so/list/v3/api/#overrideitemlayout)

```ts
overrideItemLayout?: (
  layout: { span?: number },
  item: ItemT,
  index: number,
  maxColumns: number,
  extraData?: any
) => void;
```

Customize multi-column item layout (for example, setting `span`) before positions are computed.

### [progressViewOffset](https://legend.so/list/v3/api/#progressviewoffset)

```ts
progressViewOffset?: number | undefined;
```

React Native only. Offset in pixels for the refresh indicator.

### [ref](https://legend.so/list/v3/api/#ref)

```ts
ref?: React.Ref<LegendListRef>;
```

Used to call `scrollTo` [methods](https://legend.so/list/v3/api/#ref-methods).

### [refreshing](https://legend.so/list/v3/api/#refreshing)

```ts
refreshing?: boolean;
```

React Native only. Set this true while waiting for new data from a refresh.

See [React Native Docs](https://reactnative.dev/docs/flatlist#refreshing).

### [refScrollView](https://legend.so/list/v3/api/#refscrollview)

```ts
refScrollView?: React.Ref<any>;
```

Ref to the underlying scroll container instance.

### [renderScrollComponent](https://legend.so/list/v3/api/#renderscrollcomponent)

```ts
renderScrollComponent?: (props: ScrollViewProps) => ReactElement | null
```

Render a custom scroll component. On React Native this is typically a `ScrollView`; on web this is the underlying DOM scroll element wrapper. On React Native, when using `stickyHeaderIndices`, provide an Animated-capable scroll component.

### [rtl](https://legend.so/list/v3/api/#rtl)

```ts
rtl?: boolean;
```

Forces right-to-left layout behavior for this list instance. When omitted, React Native uses `I18nManager.isRTL`; the DOM renderer defaults to `false`.

This is mainly useful for horizontal lists when you need one list to override the app-level RTL direction.

### [scrollElement](https://legend.so/list/v3/api/#scrollelement)

```ts
scrollElement?: HTMLElement | null;
```

Web only. Uses an ancestor element's scrollbar. Pass a state-backed callback ref value; `null` waits for the element to mount and `undefined` uses the list's own scrollbar. Takes precedence over `useWindowScroll` and cannot be combined with `renderScrollComponent`.

### [snapToIndices](https://legend.so/list/v3/api/#snaptoindices)

```ts
snapToIndices?: number[];
```

An array of item indices that become snap points. LegendList converts those indices into scroll offsets and passes them to the underlying scroll container as snap offsets.

On web, snap targets can point to items outside the currently mounted DOM window. LegendList computes the offsets from list measurements and estimates, so sparse snap targets can still work with virtualization.

Use `getFixedItemSize` when snap targets have exact fixed sizes. For dynamic rows, `estimatedItemSize` is only an initial offset hint until rows are measured; it is usually worth setting only when rows differ significantly from the default `100px`.

### [stickyHeaderConfig](https://legend.so/list/v3/api/#stickyheaderconfig)

```ts
stickyHeaderConfig?: {
  offset?: number;
  backdropComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;
};
```

Configures sticky header behavior:

-   `offset`: sticky top offset (for fixed toolbars/navbars)
-   `backdropComponent`: optional backdrop rendered behind sticky header

### [stickyHeaderIndices](https://legend.so/list/v3/api/#stickyheaderindices)

```ts
stickyHeaderIndices?: number[];
```

An array of indices for items that should stick to the top of the list while scrolling. Sticky headers remain visible at the top of the viewport as you scroll past them. Not supported with `horizontal={true}`.

### [style](https://legend.so/list/v3/api/#style)

```ts
style?: StyleProp<ViewStyle>;
```

Style applied to the underlying ScrollView. On web this maps to the scroll container’s CSS style.

### [useWindowScroll](https://legend.so/list/v3/api/#usewindowscroll)

```ts
useWindowScroll?: boolean; // default: false
```

Web only. When true, LegendList listens to window/body scrolling instead of rendering its own scrollable container.

`useWindowScroll` cannot be combined with `renderScrollComponent`.

### [viewabilityConfig](https://legend.so/list/v3/api/#viewabilityconfig)

```ts
viewabilityConfig?: ViewabilityConfig;
```

Configuration for when to update the `onViewableItemsChanged` callback.

See [React Native Docs](https://reactnative.dev/docs/flatlist#viewabilityconfig).

### [viewabilityConfigCallbackPairs](https://legend.so/list/v3/api/#viewabilityconfigcallbackpairs)

```ts
viewabilityConfigCallbackPairs?: ViewabilityConfigCallbackPairs<ItemT> | undefined;
```

List of `ViewabilityConfig`/`onViewableItemsChanged` pairs. A specific `onViewableItemsChanged` will be called when its corresponding `ViewabilityConfig`'s conditions are met.

See [React Native Docs](https://reactnative.dev/docs/flatlist#viewabilityconfigcallbackpairs).

  

## [SectionList](https://legend.so/list/v3/api/#sectionlist)

Legend List ships a SectionList-compatible component built on the same virtualization core. This entrypoint depends on React Native.

```tsx
import { SectionList } from "@legendapp/list/section-list";
```

### [Quick example](https://legend.so/list/v3/api/#quick-example)

```tsx
import { Text } from "react-native";
import { SectionList } from "@legendapp/list/section-list";

const sections = [
  { title: "A", data: ["Apple", "Avocado"] },
  { title: "B", data: ["Banana", "Blueberry"] },
];

const Header = ({ title }: { title: string }) => <Text>{title}</Text>;
const Row = ({ label }: { label: string }) => <Text>{label}</Text>;

export function MySectionList() {
  return (
    <SectionList
      sections={sections}
      keyExtractor={(item) => item}
      renderSectionHeader={({ section }) => <Header title={section.title} />}
      renderItem={({ item }) => <Row label={item} />}
      stickySectionHeadersEnabled
      estimatedItemSize={48}
    />
  );
}
```

### [Behavior and API](https://legend.so/list/v3/api/#behavior-and-api)

-   Mirrors React Native `SectionList` props: `sections`, `renderSectionHeader`, `renderSectionFooter`, separators, `stickySectionHeadersEnabled`, and `scrollToLocation`.
-   Accepts shared LegendList performance props like `recycleItems`, `maintainScrollAtEnd`, `drawDistance`, and `experimental_adaptiveRender`.
-   Manages `stickyHeaderIndices` internally.

Common SectionList-specific props:

```ts
type SectionListProps<ItemT, SectionT> = {
  ItemSeparatorComponent?: ComponentType<SectionListSeparatorProps<ItemT, SectionT>> | null;
  SectionSeparatorComponent?: ComponentType<SectionListSeparatorProps<ItemT, SectionT>> | ReactElement | null;
  getFixedItemSize?: (info:
    | (SectionListRenderItemInfo<ItemT, SectionT> & { type: "item" })
    | { section: SectionListData<ItemT, SectionT>; type: "header" }
    | { section: SectionListData<ItemT, SectionT>; type: "footer" }
    | (SectionListSeparatorProps<ItemT, SectionT> & { type: "item-separator" })
    | (SectionListSeparatorProps<ItemT, SectionT> & { type: "section-separator" })
  ) => number | undefined;
  keyExtractor?: (item: ItemT, index: number) => string;
  onViewableItemsChanged?: SectionListOnViewableItemsChanged<ItemT, SectionT>;
  renderItem?: (info: SectionListRenderItemInfo<ItemT, SectionT>) => ReactElement | null;
  renderSectionFooter?: (info: { section: SectionListData<ItemT, SectionT> }) => ReactElement | null;
  renderSectionHeader?: (info: { section: SectionListData<ItemT, SectionT> }) => ReactElement | null;
  sections: ReadonlyArray<SectionListData<ItemT, SectionT>>;
  stickySectionHeadersEnabled?: boolean;
};
```

Notes:

-   `stickySectionHeadersEnabled` defaults to the React Native platform behavior. It is enabled by default on iOS and disabled by default on other platforms.
-   `getFixedItemSize` can return exact sizes for section items, headers, footers, item separators, and section separators. Return `undefined` for row types that should stay dynamically measured.
-   `onViewableItemsChanged` receives item tokens mapped back to `{ item, index, key, isViewable, section }`, so callbacks do not need to understand the internal flattened section rows.
-   Section and item separators receive section/item context. The `separators` helpers passed to `renderItem` (`highlight`, `unhighlight`, `updateProps`) are currently no-ops.
-   Sections may override `renderItem`, `keyExtractor`, and `ItemSeparatorComponent`; stable section `key` values keep flattened keys stable.

**New in 3.7:** `SectionListRef` also exposes `cancelScroll`, `scrollToKey`, and `anchorVisibleContent`. Key-based methods accept flattened keys, and `anchorVisibleContent` returns them:

-   Items: `${sectionKey}:item:${itemKey}`
-   Section headers/footers: `${sectionKey}:header` / `${sectionKey}:footer`
-   Item separators: `${sectionKey}:separator:${itemIndex}`
-   Section separators: `${sectionKey}:section-separator`

`sectionKey` is the section's `key`, or `section-${sectionIndex}` when omitted. `itemKey` comes from the section or list `keyExtractor`; without one, it uses `item.key`, then `item.id`, then the item index within the section, converted to a string.

### [scrollToLocation](https://legend.so/list/v3/api/#scrolltolocation)

Returns `void`. Set `itemIndex: -1` to target the section header, falling back to its first item or footer. It also accepts `viewPositionFallback` for oversized items, as described under [initialScrollIndex](https://legend.so/list/v3/api/#initialscrollindex).

```ts
ref.current?.scrollToLocation({
  sectionIndex: 2,
  itemIndex: 10,
  viewPosition: 0,
  viewOffset: 12,
  animated: true,
});
```

### [Limitations](https://legend.so/list/v3/api/#limitations)

-   `horizontal` disables sticky section headers.
-   `numColumns` and `columnWrapperStyle` are not supported (SectionList is always one column).
-   `stickyHeaderIndices` is managed internally.

  

## [Ref Methods](https://legend.so/list/v3/api/#ref-methods)

* * *

**New in 3.7:** All scroll methods returning `Promise<void>` (`scrollToIndex`, `scrollToOffset`, `scrollToItem`, `scrollToKey`, `scrollToEnd`, `scrollIndexIntoView`, and `scrollItemIntoView`) resolve when the scroll lands, is interrupted by user input, is canceled or replaced, or cannot reach its target. Removed targets, dataset replacement, and unmounting also settle pending requests. Resolution does not guarantee arrival at the requested target.

On empty data, index-based navigation and `scrollToEnd` resolve without scrolling, even with a header or footer.

### [anchorVisibleContent](https://legend.so/list/v3/api/#anchorvisiblecontent)

**New in 3.7**

```ts
anchorVisibleContent(options?: {
  key?: string;
  hold?: boolean;
}): { key: string; offset: number } | undefined;
```

Captures an item's key and viewport offset and holds it through layout changes by default. Omit `key` to anchor the first visible item, or pass a stable item key to choose one. Pass `hold: false` for a read-only snapshot without changing scrolling or anchoring.

The returned `offset` is the viewport start minus the item's start along the scroll axis, so it is positive when the item's start is before the viewport. Returns `undefined` before initial placement, during dataset replacement, or when no item is found. While following at the end, it returns the anchor without interrupting end following. Otherwise, holding the anchor interrupts `maintainScrollAtEnd`, including retained intent while it is paused.

```ts
const anchor = listRef.current?.anchorVisibleContent();
const snapshot = listRef.current?.anchorVisibleContent({ hold: false });
```

See [Chat interfaces](https://legend.so/list/v3/guides#chat-interfaces) for preserving the reader's position while content changes.

### [cancelScroll](https://legend.so/list/v3/api/#cancelscroll)

**New in 3.7**

```ts
cancelScroll(): void;
```

Stops queued and active scrolling at the observed offset and resolves the pending scroll promise. It also releases automatic end following. Following can resume after about 500 ms if scrolling has settled within `maintainScrollAtEndThreshold`.

```ts
listRef.current?.cancelScroll();
```

### [clearCaches](https://legend.so/list/v3/api/#clearcaches)

```ts
clearCaches(options?: { mode?: "sizes" | "full" }): void;
```

Clears internal virtualization caches.

-   `sizes` (default): clears size/average caches and recalculates item positions.
-   `full`: also clears key/index/position caches.

Useful if you know cached measurements are stale after major data/layout changes.

### [flashScrollIndicators](https://legend.so/list/v3/api/#flashscrollindicators)

```ts
flashScrollIndicators(): void;
```

Asks the underlying scroll component to briefly show its scroll indicators.

### [getAnimatableRef](https://legend.so/list/v3/api/#getanimatableref)

```ts
getAnimatableRef(): any;
```

Returns the underlying animatable scroll reference (platform-specific type).

### [getNativeScrollRef](https://legend.so/list/v3/api/#getnativescrollref)

```ts
getNativeScrollRef(): any;
```

Returns the underlying scroll instance (platform-specific type).

### [getScrollableNode](https://legend.so/list/v3/api/#getscrollablenode)

```ts
getScrollableNode(): any;
```

Returns the underlying native/DOM node used for scrolling.

### [getScrollResponder](https://legend.so/list/v3/api/#getscrollresponder)

```ts
getScrollResponder(): any;
```

Returns the platform scroll responder object for advanced integrations.

### [getState](https://legend.so/list/v3/api/#getstate)

```ts
getState(): LegendListState;
```

Returns a state snapshot for advanced integrations. See [getState()](https://legend.so/list/v3/api/#getstate-details) for the full type, fields, listener channels, caveats, and examples.

### [reportContentInset](https://legend.so/list/v3/api/#reportcontentinset)

```ts
reportContentInset(inset?: { top?: number; left?: number; bottom?: number; right?: number } | null): void;
```

Reports an externally measured content inset (merged with props/native insets). Pass `null`/`undefined` to clear.

### [scrollIndexIntoView](https://legend.so/list/v3/api/#scrollindexintoview)

Scrolls an item, or a region inside it, into the available viewport. `animated` defaults to `true`.

**New in 3.7:** `itemOffset` is the region's start measured from the item's start along the scroll axis; `itemLength` is its length along that axis. Passing either enables region targeting, and the omitted value defaults to `0`. For example, `itemOffset` alone targets a point inside the item. The region should be within the item. Omit both to retain whole-item behavior.

`viewportMargin` keeps space clear at the start and end of the viewport along the scroll axis (top/bottom for vertical lists; also supported on horizontal lists). Both margins default to `0`. Margins reserve viewport space for this request; they do not add content padding.

-   A region fully visible inside the margins does not scroll.
-   A region extending above/before the available viewport aligns its start to the start margin.
-   A region extending below/after it aligns its end to the end margin.
-   A region larger than the available space aligns its start, even if it already spans that space.

When both region parameters are omitted, a whole item that already spans the available viewport does not scroll. Otherwise, whole items use the same start/end alignment rules. The reachable scroll range can limit alignment near content edges.

```ts
scrollIndexIntoView(params: {
  animated?: boolean | undefined;
  index: number;
  itemOffset?: number;
  itemLength?: number;
  viewportMargin?: { start?: number; end?: number };
}): Promise<void>;
```

```tsx
import { useRef } from "react";
import { Button, Text } from "react-native";
import { LegendList, type LegendListRef } from "@legendapp/list/react-native";

const data = Array.from({ length: 30 }, (_, index) => ({
  id: String(index),
  title: `Item ${index}`,
}));

export function ScrollExample() {
  const listRef = useRef<LegendListRef>(null);

  return (
    <>
      <Button
        title="Scroll to item 10"
        onPress={() => listRef.current?.scrollIndexIntoView({
          index: 10,
          animated: true,
          viewportMargin: { start: 48, end: 16 },
        })}
      />
      <LegendList
        ref={listRef}
        style={{ flex: 1 }}
        data={data}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => <Text>{item.title}</Text>}
      />
    </>
  );
}
```

### [scrollItemIntoView](https://legend.so/list/v3/api/#scrollitemintoview)

Scrolls an item, or a region inside it, into view. Pass the same item reference that is present in `data`. `animated` defaults to `true`.

**New in 3.7:** Accepts `itemOffset`, `itemLength`, and `viewportMargin` with the same [region and alignment rules as scrollIndexIntoView](https://legend.so/list/v3/api/#scrollindexintoview). `itemOffset` is measured from the item's start along the scroll axis; `itemLength` is the region's length along that axis. Passing either region parameter enables region targeting; the other defaults to `0`. Omit both to reveal the whole item.

A fully visible region does not scroll; a region above/before the available viewport aligns to the start margin, and one below/after it aligns to the end margin. A region larger than the space inside the margins aligns its start. With whole-item targeting, an item already spanning that space stays in place. Both margins default to `0`, apply to vertical and horizontal lists, and reserve viewport space without adding content padding. The reachable scroll range can limit alignment near content edges.

```ts
scrollItemIntoView(params: {
  animated?: boolean | undefined;
  item: any;
  itemOffset?: number;
  itemLength?: number;
  viewportMargin?: { start?: number; end?: number };
}): Promise<void>;
```

```jsx
import { useRef } from "react";
import { Button } from "react-native";
import { LegendList } from "@legendapp/list/react-native";

export function ScrollToItemExample() {
  const listRef = useRef(null);
  const targetItem = data.find((item) => item.id === "item-5");

  const scrollToSpecificItem = () => {
    if (targetItem) {
      listRef.current?.scrollItemIntoView({ item: targetItem });
    }
  };

  return (
    <>
      <Button title="Scroll to target item" onPress={scrollToSpecificItem} />
      <LegendList
        ref={listRef}
        data={data}
        renderItem={renderItem}
      />
    </>
  );
}
```

### [scrollToEnd](https://legend.so/list/v3/api/#scrolltoend)

```ts
scrollToEnd(params?: {
  animated?: boolean,
  viewOffset?: number,
}): Promise<void>;
```

Scrolls to the end of the list.

Valid parameters:

-   _animated_ (boolean) - Whether the list should do an animation while scrolling. Defaults to true.

### [scrollToIndex](https://legend.so/list/v3/api/#scrolltoindex)

```ts
scrollToIndex(params: {
  index: number;
  animated?: boolean;
  viewOffset?: number;
  viewPosition?: number;
  viewPositionFallback?: "start" | "end";
}): Promise<void>;
```

Scrolls to the item at the specified index. For the most accurate initial offset, use [getFixedItemSize](https://legend.so/list/v3/api/#getfixeditemsize) for truly fixed-size rows. Dynamic rows stabilize through measurement; `estimatedItemSize` is only an initial hint and is most useful when rows differ significantly from the default `100px`. `viewPositionFallback` handles oversized items as described under [initialScrollIndex](https://legend.so/list/v3/api/#initialscrollindex).

**New in 3.7:** Out-of-range indices clamp to the first or last item; the promise resolves once the clamped scroll lands (or is interrupted, canceled, or unreachable). Empty data resolves without scrolling.

### [scrollToItem](https://legend.so/list/v3/api/#scrolltoitem)

```ts
scrollToItem(params: {
  animated?: boolean,
  item: Item,
  viewOffset?: number;
  viewPosition?: number;
  viewPositionFallback?: "start" | "end";
}): Promise<void>;
```

Requires linear scan through data - use [scrollToIndex](https://legend.so/list/v3/api/#scrolltoindex) instead if possible. Provided for compatibility with FlatList only.

Pass the same item reference that is present in `data`. `viewPositionFallback` handles oversized items as described under [initialScrollIndex](https://legend.so/list/v3/api/#initialscrollindex).

Valid parameters:

-   _animated_ (boolean) - Whether the list should do an animation while scrolling. Defaults to true.
-   _item_ (object) - The item to scroll to. Required.

### [scrollToKey](https://legend.so/list/v3/api/#scrolltokey)

**New in 3.7**

```ts
scrollToKey(params: {
  key: string;
  animated?: boolean;
  viewOffset?: number;
  viewPosition?: number;
}): Promise<void>;
```

Scrolls to a stable item key from `keyExtractor`, retaining the target through prepends and same-key item updates. `animated` defaults to `true`; `viewPosition` and `viewOffset` control alignment as with [scrollToIndex](https://legend.so/list/v3/api/#scrolltoindex). If the key is missing or removed, the promise resolves without reaching that item.

```ts
await listRef.current?.scrollToKey({
  key: "message-42",
  animated: true,
  viewPosition: 0.5,
});
```

For SectionList, use the flattened item key `${sectionKey}:item:${itemKey}`. See [SectionList](https://legend.so/list/v3/api/#sectionlist).

### [scrollToOffset](https://legend.so/list/v3/api/#scrolltooffset)

```ts
scrollToOffset(params: {
  offset: number;
  animated?: boolean;
}): Promise<void>;
```

Scroll to a specific content pixel offset in the list.

**New in 3.7:** Out-of-range offsets clamp to the reachable scroll range. The promise resolves once the clamped scroll lands (or is interrupted, canceled, or unreachable).

Valid parameters:

-   _offset_ (number) - The offset to scroll to. In case of horizontal being true, the offset is the x-value, in any other case the offset is the y-value. Required.
-   _animated_ (boolean) - Whether the list should do an animation while scrolling. Defaults to false.

### [setItemSize](https://legend.so/list/v3/api/#setitemsize)

```ts
setItemSize(itemKey: string, size: { height: number; width: number }): void;
```

Updates a known item's measured size directly and recalculates positions as needed. Use this when an item's content changes outside normal layout measurement, for example when media dimensions arrive from cache or a custom native surface reports its final size through a separate channel.

`itemKey` is the value returned by `keyExtractor`, so stable keys are required.

### [setScrollProcessingEnabled](https://legend.so/list/v3/api/#setscrollprocessingenabled)

```ts
setScrollProcessingEnabled(enabled: boolean): void;
```

Enables or disables scroll processing. Useful when you need to temporarily opt out of list virtualization behavior.

### [setVisibleContentAnchorOffset](https://legend.so/list/v3/api/#setvisiblecontentanchoroffset)

```ts
setVisibleContentAnchorOffset(value: number | ((current: number) => number)): void;
```

Adjusts the internal anchor offset used by `maintainVisibleContentPosition`. Useful for advanced scroll anchoring behavior.

  

## [Hooks](https://legend.so/list/v3/api/#hooks)

Hooks are exported from both `@legendapp/list/react-native` and `@legendapp/list/react`.

### [useAdaptiveRender](https://legend.so/list/v3/api/#useadaptiverender)

```ts
useAdaptiveRender: () => "normal" | "light";
```

Returns the current adaptive render mode for the item being rendered. Enable the signal with [experimental\_adaptiveRender](https://legend.so/list/v3/api/#experimental_adaptiverender), then use this hook inside rows to switch expensive content to a lighter render while the list is moving quickly.

```tsx
import { useAdaptiveRender } from "@legendapp/list/react-native";

function FeedRow({ item }) {
  const mode = useAdaptiveRender();

  return (
    <RowShell item={item}>
      {mode === "light" ? <CompactPreview item={item} /> : <RichPreview item={item} />}
    </RowShell>
  );
}
```

### [useAdaptiveRenderChange](https://legend.so/list/v3/api/#useadaptiverenderchange)

```ts
useAdaptiveRenderChange: (callback: (mode: "normal" | "light") => void) => void;
```

Runs `callback` whenever the adaptive render mode changes for the current item. Use this when switching modes should drive item-local imperative work, such as pausing a chart, video, or expensive animation while fast scrolling is active.

### [useIsLastItem](https://legend.so/list/v3/api/#useislastitem)

```ts
useIsLastItem: () => boolean;
```

Returns `true` when the current rendered item is one of the list's last items. Useful for conditional spacing, CTA rows, or end-of-list UI logic.

### [useListScrollSize](https://legend.so/list/v3/api/#uselistscrollsize)

```ts
useListScrollSize: () => { width: number; height: number };
```

Returns the current scroll viewport size for the parent list. Useful when item rendering depends on viewport dimensions.

### [useRecyclingEffect](https://legend.so/list/v3/api/#userecyclingeffect)

```ts
interface LegendListRecyclingState<T> {
    item: T;
    prevItem: T | undefined;
    index: number;
    prevIndex: number | undefined;
}
useRecyclingEffect: (effect: (info: LegendListRecyclingState<unknown>) => void | (() => void)) => void;
```

`useRecyclingEffect` can be used to reset any side effects when an item gets recycled.

```tsx
import { useRef } from "react";
import { useRecyclingEffect } from "@legendapp/list/react-native"

export function ItemComponent({ item }) {
    const refSwipeable = useRef(null);
    const refVideo = useRef(null);
    // A callback when the item is recycled into a new item
    useRecyclingEffect(({ item, prevItem, index, prevIndex }) => {
        // Reset any side effects from the previous item
        refSwipeable?.current?.close();
        refVideo?.current?.reset();
    });

    // ...
}
```

### [useRecyclingState](https://legend.so/list/v3/api/#userecyclingstate)

```ts
interface LegendListRecyclingState<T> {
    item: T;
    prevItem: T | undefined;
    index: number;
    prevIndex: number | undefined;
}
useRecyclingState: <T>(
  updateState: ((info: LegendListRecyclingState<T>) => T) | T
) => [T, Dispatch<SetStateAction<T>>];
```

`useRecyclingState` automatically resets the state when an item is recycled into a new item.

```tsx
import { useRecyclingState } from "@legendapp/list/react-native"
export function ItemComponent({ item }) {
    // Like useState but it resets when the item is recycled
    const [isExpanded, setIsExpanded] = useRecyclingState(() => false);

    // ...
}
```

### [useSyncLayout](https://legend.so/list/v3/api/#usesynclayout)

```ts
useSyncLayout: () => () => void;
```

A hook for synchronizing layout operations. This is useful for advanced use cases where you need to coordinate layout updates with other components or operations.

```tsx
import { View, Text } from "react-native";
import { useSyncLayout } from "@legendapp/list/react-native"

export function ItemComponent({ item }) {
    const syncLayout = useSyncLayout();

    return (
        <View onLayout={syncLayout}>
            <Text>{item.title}</Text>
        </View>
    );
}
```

### [useViewability](https://legend.so/list/v3/api/#useviewability)

```ts
interface ViewToken<ItemT = any> {
    containerId: number;
    index: number;
    isViewable: boolean;
    item: ItemT;
    key: string;
}
useViewability: (callback: (viewToken: ViewToken) => void, configId?: string) => void;
```

A hook that provides callbacks when an item's viewability changes. This hook registers a callback that will be invoked whenever the item's visibility status changes, providing detailed information about the item through the ViewToken interface. It is similar to [onViewableItemsChanged](https://legend.so/list/v3/api/#onviewableitemschanged) but runs for only the rendering item. If you defined multiple viewability configs using [viewabilityConfigCallbackPairs](https://legend.so/list/v3/api/#viewabilityconfigcallbackpairs) then provide the id of the one you're interested in with `configId`.

Configure `viewabilityConfig` with a visibility threshold on the owning list. For `useViewability(callback, "main")`, set `viewabilityConfig.id` to `"main"`.

```tsx
import { useState } from "react";
import { View, Text } from "react-native";
import { useViewability } from "@legendapp/list/react-native"

function ExpensiveComponent() {
    return <Text>Expensive content</Text>;
}

function PlaceholderComponent() {
    return <Text>Placeholder</Text>;
}

export function ItemComponent({ item }) {
    const [isVisible, setIsVisible] = useState(false);

    useViewability((viewToken) => {
        // Called when viewability changes
        setIsVisible(viewToken.isViewable);

        if (viewToken.isViewable) {
            console.log("Item visible:", viewToken.item);
            console.log("Item index:", viewToken.index);
        }
    }, "main");

    return (
        <View>
            {isVisible ? <ExpensiveComponent /> : <PlaceholderComponent />}
        </View>
    );
}
```

### [useViewabilityAmount](https://legend.so/list/v3/api/#useviewabilityamount)

```ts
interface ViewAmountToken<ItemT = any> {
    containerId: number;
    index: number;
    isViewable: boolean;
    item: ItemT;
    key: string;
    percentOfScroller: number;
    percentVisible: number;
    scrollSize: number;
    size: number;
    sizeVisible: number;
}

useViewabilityAmount: (callback: (viewAmountToken: ViewAmountToken) => void) => void;
```

A hook that provides detailed metrics about how much of an item is visible in the viewport. The callback receives a ViewAmountToken with information like the pixel measurements (sizeVisible, size), percentage visible, and more.

Configure `viewabilityConfig` with a visibility threshold on the owning list. Percentage fields use 0–100 for visible items.

```tsx
import { useState } from "react";
import { Animated, Text } from "react-native";
import { useViewabilityAmount } from "@legendapp/list/react-native"

export function ItemComponent({ item }) {
    const [opacity, setOpacity] = useState(0);

    useViewabilityAmount((viewAmountToken) => {
        // Get detailed visibility information
        setOpacity(Math.max(0, Math.min(1, viewAmountToken.percentVisible / 100)));

        // Additional metrics available:
        // viewAmountToken.sizeVisible - pixels of item visible in viewport
        // viewAmountToken.size - total size of the item
        // viewAmountToken.percentOfScroller - what percent of the scroller this item takes up
        // viewAmountToken.scrollSize - size of the viewport
    });

    return (
        <Animated.View style={{ opacity }}>
            <Text>{item.title}</Text>
        </Animated.View>
    );
}
```

## [getState()](https://legend.so/list/v3/api/#getstate-1)

`getState()` is a function on `LegendListRef`, accessed as `ref.current?.getState()`. See its entry in [Ref Methods](https://legend.so/list/v3/api/#ref-method-getstate).

This is likely not necessary in most apps, but can power advanced functionality and customization. It is used by [KeyboardAwareLegendList](https://legend.so/list/v3/react-native/keyboard-and-animated#keyboardawarelegendlist) for example.

### [LegendListState type](https://legend.so/list/v3/api/#legendliststate-type)

```ts
type LegendListState = {
  activeStickyIndex: number;
  contentLength: number;
  data: readonly any[];
  dataKey?: Key;
  distanceFromEnd: number;
  elementAtIndex: (index: number) => any;
  end: number;
  endBuffered: number;
  getAverageItemSizes: () => Record<string, LegendListAverageItemSize>;
  isAtEnd: boolean;
  isAtStart: boolean;
  isEndReached: boolean;
  isFollowingEnd: boolean;
  isNearEnd: boolean;
  isNearStart: boolean;
  isStartReached: boolean;
  isWithinMaintainScrollAtEndThreshold: boolean;
  indexByKey: (key: string) => number | undefined;
  listen: {
    <T extends Exclude<LegendListListenerType, "isFollowingEnd">>(
      listenerType: T,
      callback: (value: Pick<ListenerTypeValueMap, LegendListListenerType>[T]) => void
    ): () => void;
    <T extends LegendListListenerType>(
      listenerType: T,
      callback: (value: Pick<ListenerTypeValueMap, LegendListListenerType>[T]) => void
    ): () => void;
  };
  listenToPosition: (key: string, callback: (value: number) => void) => () => void;
  positionAtIndex: (index: number) => number;
  positionByKey: (key: string) => number | undefined;
  scroll: number;
  scrollLength: number;
  scrollVelocity: number;
  sizeAtIndex: (index: number) => number;
  sizes: Map<string, number>;
  start: number;
  startBuffered: number;
};

type LegendListAverageItemSize = {
  average: number;
  count: number;
};
```

### [Fields and Methods](https://legend.so/list/v3/api/#fields-and-methods)

-   `activeStickyIndex`: currently active sticky item index (`-1` when none)
-   `contentLength`: content size of the list including header/footer/insets
-   `data`: current data array reference used by the list
-   `dataKey`: **New in 3.7.** The current optional dataset key, as passed to the `dataKey` prop
-   `distanceFromEnd`: **New in 3.7.** Nonnegative distance in pixels to the physical scroll edge, including the trailing inset. Computed when `getState()` is called
-   `isFollowingEnd`: **New in 3.7.** End-follow intent, including retained intent while `maintainScrollAtEnd` is paused. This is distinct from simply being near the end
-   `elementAtIndex(index)`: rendered native element for an index (if currently mapped to a container)
-   `getAverageItemSizes()`: measured average item sizes grouped by item type. Use this to inspect real sizing data when deciding whether `estimatedItemSize` is worth setting for unusually sized rows.
-   `indexByKey(key)`: cached index for a key, or `undefined` if unknown
-   `isAtStart` / `isAtEnd`: threshold-based booleans for edge-of-list state. **New in 3.7:** End checks count trailing and keyboard insets once
-   `isNearStart` / `isNearEnd`: proximity booleans based on the configured start/end thresholds
-   `isStartReached` / `isEndReached`: whether the one-shot threshold callbacks are currently latched
-   `isWithinMaintainScrollAtEndThreshold`: whether the current scroll position is close enough to the tail for `maintainScrollAtEnd` to apply
-   `listen(...)`: subscribe to selected internal state channels
-   `listenToPosition(key, ...)`: subscribe to position updates for one item key
-   `positionAtIndex(index)`: known position for an index
-   `positionByKey(key)`: known position for an item key (if available)
-   `scroll`: current scroll offset
-   `scrollLength`: viewport length along scroll axis
-   `scrollVelocity`: current estimated scroll velocity
-   `sizeAtIndex(index)`: known measured size for an index
-   `sizes`: key-to-size map of known measured item sizes
-   `start` / `end`: visible range bounds without buffer
-   `startBuffered` / `endBuffered`: virtualized range bounds including draw buffer

### [Listen Channels](https://legend.so/list/v3/api/#listen-channels)

`listen` supports these channel names:

-   `activeStickyIndex` (`number`)
-   `adaptiveRender` (`"normal" | "light"`)
-   `anchoredEndSpaceSize` (`number`)
-   `footerSize` (`number`)
-   `headerSize` (`number`)
-   `isAtEnd` (`boolean`)
-   `isAtStart` (`boolean`)
-   `isFollowingEnd` (`boolean`) — **New in 3.7**
-   `isNearEnd` (`boolean`)
-   `isNearStart` (`boolean`)
-   `isWithinMaintainScrollAtEndThreshold` (`boolean`)
-   `lastItemKeys` (`string[]`)
-   `lastPositionUpdate` (`number`)
-   `numContainers` (`number`)
-   `numContainersPooled` (`number`)
-   `otherAxisSize` (`number`)
-   `readyToRender` (`boolean`)
-   `snapToOffsets` (`number[]`)
-   `totalSize` (`number`)

**New in 3.7:** `listen("isFollowingEnd", callback)` subscribes to end-follow intent. `distanceFromEnd` is not a listen channel; read a fresh `getState().distanceFromEnd` snapshot when needed.

### [Caveats](https://legend.so/list/v3/api/#caveats)

-   Scalar fields are snapshots at the time of the call. Call `getState()` again to refresh them; accessor methods and `sizes` read live state.
-   `positionAtIndex` and `sizeAtIndex` assume the item has been measured; for unmeasured items values may be unavailable.
-   `positionByKey` can return `undefined` if a key is unknown or not measured yet.
-   `elementAtIndex` can return `null`/`undefined` when the item is not currently rendered.
-   `sizes` is a live `Map` reference that updates as list state changes.
-   `getAverageItemSizes()` only reports types that have measured items. Items without a `getItemType` value are grouped under `default`.

### [Examples](https://legend.so/list/v3/api/#examples)

```tsx
import { useEffect, useRef } from "react";
import { LegendList, type LegendListRef } from "@legendapp/list/react-native";

function StateSnapshotExample() {
  const ref = useRef<LegendListRef>(null);

  useEffect(() => {
    const state = ref.current?.getState();
    if (!state) return;
    console.log("scroll", state.scroll, "velocity", state.scrollVelocity);
    console.log("visible range", state.start, state.end);
  }, []);

  return <LegendList ref={ref} data={data} renderItem={renderItem} />;
}
```

```tsx
import { useEffect, useRef } from "react";
import { LegendList, type LegendListRef } from "@legendapp/list/react-native";

function ListenerExample() {
  const ref = useRef<LegendListRef>(null);

  useEffect(() => {
    const state = ref.current?.getState();
    if (!state) return;

    const unsubscribeTotal = state.listen("totalSize", (total) => {
      console.log("total size changed", total);
    });
    const unsubscribeSticky = state.listen("activeStickyIndex", (index) => {
      console.log("active sticky index", index);
    });

    return () => {
      unsubscribeTotal();
      unsubscribeSticky();
    };
  }, []);

  return <LegendList ref={ref} data={data} renderItem={renderItem} />;
}
```

```tsx
import { useEffect, useRef } from "react";
import { LegendList, type LegendListRef } from "@legendapp/list/react-native";

function PositionListenerExample() {
  const ref = useRef<LegendListRef>(null);

  useEffect(() => {
    const state = ref.current?.getState();
    if (!state) return;
    const unsubscribe = state.listenToPosition("message-42", (position) => {
      console.log("message-42 position", position);
    });
    return unsubscribe;
  }, []);

  return <LegendList ref={ref} data={data} keyExtractor={(item) => item.id} renderItem={renderItem} />;
}
```

```tsx
import { useEffect, useRef } from "react";
import { LegendList, type LegendListRef } from "@legendapp/list/react-native";

function AverageSizeExample() {
  const ref = useRef<LegendListRef>(null);

  useEffect(() => {
    const state = ref.current?.getState();
    if (!state) return;

    const unsubscribe = state.listen("lastPositionUpdate", () => {
      console.log("average item sizes", state.getAverageItemSizes());
    });

    return unsubscribe;
  }, []);

  return <LegendList ref={ref} data={data} renderItem={renderItem} />;
}
```

  

[View Markdown](https://legend.so/list/v3/api.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+API+Reference&body=Page%3A+https%3A%2F%2Flegend.so%2Flist%2Fv3%2Fapi%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A)
