Lynx/Modules/List/API reference
@sigx/lynx-list · Beta

API reference#

Exports of @sigx/lynx-list v0.27.0.

Exports#

ExportKindPurpose
ListcomponentThe virtualized list.
ListMethodsobjectImperative scroll methods for a captured element.
ListProps<T>typeProps of List, generic over the item type.
ListReftypeThe mtRef type — a main-thread ref to the native element.
ListTypetype'single' | 'flow' | 'waterfall'.
ListItemSnaptype'start' | 'center' | 'end' | { factor?: number; offset?: number; maxSnapCount?: number } — see itemSnap.
ScrollAligntype'top' | 'bottom' | 'middle'.
ScrollToIndexOptionstypeOptions for ListMethods.scrollToIndex.
TSX
import { List, ListMethods } from '@sigx/lynx-list';
import type { ListProps, ListRef } from '@sigx/lynx-list';

<List> props#

items and renderItem are the only required props; both are generic over the element type T of items.

PropTypeNotes
itemsreadonly T[]Required. The data.
renderItem(item: T, index: number) => JSXRequired. Per-cell renderer.
keyExtractor(item: T, index: number) => stringStable recycler key (item-key). Defaults to the index — set it for lists that mutate.
itemType(item: T, index: number) => stringRecycle-pool selector (item-type) for mixed cell shapes.
estimatedItemSizenumberMain-axis px estimate for uniform rows; improves scroll accuracy. Omit for variable-height content (chat) — a too-small estimate clips items until measured.
initialMainAxisSizenumberPins the native list's main-axis px on the first frame instead of the 1px placeholder; the live measure still wins once it lands. Use it when you know the height up front (a keyboard panel, a fixed grid).
horizontalbooleanHorizontal scrolling.
numColumnsnumberGrid columns (span-count).
listTypeListTypeLayout mode: single (default), flow, waterfall.
itemSnapListItemSnapPaginated snap: the alignment shorthand 'start' / 'center' / 'end', or { factor, offset, maxSnapCount }. Omit the prop to disable snapping. See below.
onEndReachedThresholdnumberItems-from-end at which onEndReached fires.
onStartReachedThresholdnumberItems-from-start at which onStartReached fires.
loadingMorebooleanShow a trailing loading cell (infinite scroll).
refreshingbooleanControlled pull-to-refresh state; passing it opts in (vertical only).
pullThresholdnumberPull distance (px) that triggers a refresh. Default 64.
invertedbooleanChat mode: bottom-anchored + stick-to-bottom (vertical only).
stickToBottombooleanIn chat mode, auto-scroll on new items when at the bottom. Default true. While at the bottom the pin is re-applied on every layoutcomplete, so late cell growth cannot strand the viewport.
newMessagesOffsetnumberRaises the chat newMessages affordance off the wrapper's bottom edge. Default 12 — enough to clear nothing, so set it to clear a floating composer.
bottomInsetnumber | SharedValue<number>Bottom content inset the recycler keeps clear, applied natively with no CSS write and no layout pass — so a SharedValue can drive it per frame. See below.
windowSizenumberEnables windowing: render only this many cells of a long items.
pageSizenumberItems revealed per scroll-edge page when windowing. Default 30.
maxWindownumberCap on rendered window length; the far end trims past it. Default max(120, windowSize × 2).
itemsKeystringDataset identity. When it changes, items is treated as a brand-new list: the window re-anchors to its initial position and scroll resets to the start (bottom in chat mode). Omit it for append / prepend / edit of the same logical list.
mtRefListRefCapture the native element for ListMethods.
class / styleApplied to the measuring wrapper <view>.

Events#

EventPayloadFires
onEndReachedWithin onEndReachedThreshold items of the end.
onStartReachedWithin onStartReachedThreshold items of the start.
onScroll{ offset: number }On scroll, with the current offset.
onRefreshOn a pull-to-refresh past pullThreshold.

Slots#

Passed through the slots prop.

SlotSignatureRenders
header() => JSXA full-span cell before the items.
footer() => JSXA full-span cell after the items.
empty() => JSXIn place of the list when items is empty.
refresh() => JSXCustom pull-to-refresh indicator.
newMessages({ count }) => JSXChat-mode "new messages" affordance while scrolled up.

ListMethods#

Typed wrappers around the native scroll UI methods, callable from any main-thread event handler. Both accept el | null, so you can pass ref.current directly; a null element is a silent no-op.

TSX
ListMethods.scrollToTop(el);                          // jump to top
ListMethods.scrollToTop(el, { smooth: true });        // animated
ListMethods.scrollToIndex(el, 12, { align: 'middle', smooth: true });
  • scrollToTop(el, opts?) — scroll to the first cell (the top of the list, header included).
  • scrollToIndex(el, index, opts?) — scroll so the cell at index aligns per opts.align. index is the rendered cell index, not the data index — add 1 to a data index when a header slot is present.

ScrollToIndexOptions:

FieldTypeDefaultNotes
alignScrollAlign'top'Align the target cell to top, bottom, or middle.
offsetnumber0Extra pixel offset applied after alignment.
smoothbooleanfalseAnimate the scroll (default is a jump).

scrollHandle#

ListMethods needs the native element. scrollHandle is the prop form — pass an identity-stable handle and the List populates it at setup:

TSX
// Module or component scope — NOT inside render. The handle must keep its identity.
const handle: ListScrollHandle = {};

<List scrollHandle={handle}  />

handle.scrollToEnd?.();                  // jump to the newest
handle.scrollToEnd?.({ smooth: true });

scrollToEnd is the one piece of index math you cannot do yourself once windowing is on: it accounts for the header, footer and loading cells and the rendered window. In chat mode it also marks the viewport at-bottom and clears the unread affordance, so a target cell that has not reached native yet is caught by the next relayout's re-pin.

The handle must be identity-stable — build it once, outside render.

itemSnap#

Snaps cells into place when a paginated scroll settles — the native item-snap contract, which both platforms read as a dictionary.

TypeScript
type ListItemSnap = 'start' | 'center' | 'end' | {
  /** Where the item settles, as a fraction of the viewport: 0 start, 0.5 centre, 1 end. Default 0. */
  factor?: number;
  /** Pixel offset from the alignment line. Default 0. */
  offset?: number;
  /** How many items a single fling may cross (Lynx 4.0+). Default 1. */
  maxSnapCount?: number;
};

The shorthand maps to factor 0 / 0.5 / 1. A factor outside [0, 1] is corrected to 0 and a maxSnapCount below 1 to 1 — what native does — with a dev-build warning. maxSnapCount is a bound, not a forced distance: a slow fling still moves one page; a fast one may page up to that many. Hosts older than Lynx 4.0 ignore it. To disable snapping, omit the prop — an absent item-snap is what native means by "no paging".

TSX
<List horizontal itemSnap="center" />
<List horizontal itemSnap={{ factor: 0, offset: 16, maxSnapCount: 3 }}  />

Sticky section headers (sticky-top / sticky-bottom) use the engine's sticky layout; pluginSigxLynx({ enableNewSticky: true }) opts into Lynx 4.0's new implementation, which changes where and when a header pins — see the plugin's page-config flags.

bottomInset#

The recycler keeps bottomInset pixels of its bottom viewport clear. It is applied natively, as a padding/inset delta rather than a CSS write, so there is no layout pass and a main-thread SharedValueuseKeyboardLiftSV(), a BottomSheet's onReveal — can drive it every frame:

TSX
const lift = useKeyboardLiftSV();

<List inverted bottomInset={lift}  />

In chat mode the newest item stays anchored above the inset via same-frame native compensation, and at-bottom detection becomes inset-aware — so a compensation scroll cannot falsely release the pin or fire a spurious endReached. The native compensation is gated on the list being pinned at the bottom, which is why the first paint of a chat thread lands on the newest message on both platforms: the pin is established before the inset is applied. No paddingBottom workaround is needed on either platform.

This is the package's first native side. Run sigx prebuild after upgrading. Hosts without the native module — the web target — ignore the inset rather than failing.

See Usage for worked examples of each feature.