# Sticky header list

> A sectioned list whose sticky headers change as they stick: the title shrinks toward the leading edge and a surface with a hairline (frosted on the web) fades in under it. A contacts-style index on the side jumps between sections while a large letter follows the finger, with a tick per letter.

- Type: Component (data)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/sticky-header-list
- Markdown: https://goodui.dev/native/components/sticky-header-list.md
- Registry item: https://goodui.dev/native/r/sticky-header-list.json
- Dependencies: react-native-gesture-handler, react-native-reanimated
- Keywords: react native section list, sticky section headers, alphabet index, contacts list, section index scrubber, a to z list, grouped list by date, expo sectionlist

## When to use

- Long alphabetical lists the person scans or jumps through: contacts, countries, ingredients, members.
- Feeds grouped by date where the current day should stay visible: transactions, messages, activity.
- Any long list with groups that benefit from a visible group title while scrolling.

## When not to use

- A short settings list in a few groups: use list-row with ListSection.
- Rows of different heights: this list jumps by fixed itemHeight and headerHeight; use a plain SectionList.
- Collapsible groups: use accordion.
- Rows the person sorts by hand: use reorderable-list.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/sticky-header-list.json
npx expo install --fix
```

The component lands in `components/goodui-native/sticky-header-list/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs locale.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { Text, View } from "react-native";
import { StickyHeaderList, type StickySection } from "@/components/goodui-native/sticky-header-list/sticky-header-list";

type Contact = { id: string; name: string };

export function Contacts({ sections }: { sections: StickySection<Contact>[] }) {
  return (
    <StickyHeaderList
      accessibilityLabel="Contacts"
      sections={sections}
      keyExtractor={(c) => c.id}
      itemHeight={56}
      style={{ flex: 1 }}
      renderItem={({ item }) => (
        <View style={{ paddingHorizontal: 16 }}>
          <Text>{item.name}</Text>
        </View>
      )}
    />
  );
}
```

## Examples

### Full alphabet with empty letters

```tsx
<StickyHeaderList
  sections={contacts}
  indexLetters={"ABCDEFGHIJKLMNOPQRSTUVWXYZ#".split("")}
  keyExtractor={(c) => c.id}
  ListHeaderComponent={<SearchBar placeholder="Search contacts" />}
  renderItem={({ item }) => <ContactRow contact={item} />}
/>
```

### Transactions by day, no index

```tsx
<StickyHeaderList
  sections={days}
  showIndex={false}
  keyExtractor={(t) => t.id}
  renderItem={({ item }) => <TransactionRow txn={item} />}
/>
```

## API reference

### StickyHeaderList

`import { StickyHeaderList } from "@/components/goodui-native/sticky-header-list/sticky-header-list";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `sections` (required) | `StickySection<T>[]` | – | Sections in display order. |
| `renderItem` (required) | `(info: { item: T; index: number; section: StickySection<T> }) => ReactNode` | – | Renders one row, `itemHeight` tall. |
| `keyExtractor` (required) | `(item: T, index: number) => string` | – | Stable key per row. |
| `itemHeight` | `number` | `56` | Height of every row in points, so the list and the index can jump without measuring. Default 56. |
| `headerHeight` | `number` | `40` | Height of a section header in points. Default 40. |
| `showIndex` | `boolean \| "auto"` | `"auto"` | Side index for jumping between sections (contacts style). `auto` (default) shows it with six sections or more. |
| `indexLetters` | `string[]` | – | Letters in the index. Default: the first character of each section title. Letters without a section show dimmed and jump to the next one. |
| `separators` | `boolean` | `true` | Hairlines between rows, inset to the content. Default `true`. |
| `haptic` | `boolean` | `true` | `false` silences the index ticks. |
| `ListHeaderComponent` | `ReactElement` | – | Content above the first section, such as a search field or a count. It scrolls away. |
| `ListEmptyComponent` | `ReactElement` | – | Shown when there are no sections. |
| `accessibilityLabel` | `string` | – | Name of the list, for example "Contacts". |
| `labels` | `Partial<StickyHeaderListLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style of the outer container. Give it `flex: 1` or a height. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- Section headers have the header role, so screen readers can move by heading.
- The index is one adjustable element named 'Section index' whose value is the current letter; increment and decrement move to the next and previous section that exists, and the letter is announced.
- On the web the index is a focusable slider with aria-valuenow, aria-valuemin, aria-valuemax and aria-valuetext; arrow keys move between sections.
- The large letter bubble is decorative and hidden from screen readers.

## Motion

- Each header's stuck look is interpolated from the scroll position on the UI thread over 12 pt: the title scales to 0.8 from its leading edge and the stuck surface with its hairline fades in; scrolling back reverses it.
- On the web the stuck surface turns translucent over a backdrop blur, so rows scrolling under it frost.
- Index: touching it springs a 56 pt letter bubble out beside the finger (snappy), which follows the finger and fades away on release; the list jumps without animation, like the system contacts list.
- Reduced motion: the bubble fades in without scaling; the stuck transition is tied to scrolling and stays.

## Haptics

- select for each letter the finger crosses on the index and for each keyboard or screen reader step (haptic={false} silences it)

## Performance

- Built on SectionList (virtualized) with getItemLayout from the fixed heights, so jumps to any section are exact and instant.
- The scroll handler and every header animation run on the UI thread; React state changes only when the index letter changes.

## Notes for AI

- Every row is itemHeight tall and every header headerHeight tall; size renderItem to fit. Wrong heights make index jumps land off target.
- Give the list a bounded height: style={{ flex: 1 }} in a screen or a fixed height in a card.
- Use ListHeaderComponent (not contentContainerStyle padding) for content above the first section, such as a search field; it is measured for the jump offsets.
- Section titles feed the index by their first character. Pass indexLetters for a full A to Z: missing letters show dimmed and jump to the next section.
- Rows get a hairline between them by default (separators); renderItem should not add its own border.

## Related

- [List row](https://goodui.dev/native/components/list-row.md): Settings and navigation cells with a token-colored icon tile, title, subtitle and a trailing value, switch, badge, check or chevron, grouped in inset sections with headers and footers. On touch down the highlight floods in, the tile sinks and the chevron leans forward; on release the highlight lingers and fades.
- [Search bar](https://goodui.dev/native/components/search-bar.md): An iOS-style search field: focusing it shrinks the field while a Cancel button springs in beside it and scope chips cascade in below. Clear button, loading spinner and debounced queries built in.
- [Large title header](https://goodui.dev/native/components/large-title-header.md): An iOS large title that collapses into a compact bar as the content scrolls: the titles cross-fade, the bar fades in, the title stretches when pulled past the top and a half-finished collapse settles on either state.
- [Reorderable list](https://goodui.dev/native/components/reorderable-list.md): A drag-to-sort list: a long press (or the grip) lifts a row with a little scale and a shadow, the other rows slide out of its way with a tick per swap, the list scrolls itself near the edges, and the row settles into its slot on release.

## Guidance for AI tools

Sticky header list: A sectioned list whose sticky headers change as they stick: the title shrinks toward the leading edge and a surface with a hairline (frosted on the web) fades in under it. A contacts-style index on the side jumps between sections while a large letter follows the finger, with a tick per letter. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
