useScrollSpy

Collect headings from the DOM or title registry and track which section is currently visible.

Use the title registry to power a live-updating table of contents that highlights the section currently in view.

Loading demo…

import { ScrollView, View } from 'react-native'; import { Badge, Block, Row, Text, TitleRegistryProvider, useScrollSpy, useTitleRegistration } from '@plocks/ui'; const SECTIONS = [ { title: 'Overview', order: 1, copy: 'Introduce the feature and set expectations for what the rest of the document covers.' }, { title: 'Installation', order: 2, copy: 'List required packages and platform caveats so teams can get started quickly.' }, { title: 'Usage patterns', order: 3, copy: 'Highlight the most common ways to use the feature with short inline examples.' }, { title: 'Troubleshooting', order: 4, copy: 'Capture the top issues support sees and the fix steps that usually resolve them.' } ] as const; function Section({ title, order, copy }: { title: string; order: number; copy: string }) { const { elementRef, id } = useTitleRegistration({ text: title, order }); return ( <View ref={elementRef} nativeID={id}> <Text fw="semibold">{title}</Text> <Text size="sm" c="secondary">{copy}</Text> </View> ); } function TocList() { const { items, activeId } = useScrollSpy(); if (!items.length) { return <Text size="sm" c="muted">No headings detected yet.</Text>; } return ( <Row gap="xs" wrap="wrap"> {items.map(item => ( <Badge key={item.id} variant={activeId === item.id ? 'light' : 'outline'} c={activeId === item.id ? 'primary' : 'gray'} > {item.value} </Badge> ))} </Row> ); } export function Demo() { return ( <TitleRegistryProvider> <Block gap="lg"> <TocList /> <ScrollView style={{ maxHeight: 280 }} contentContainerStyle={{ gap: 24 }}> {SECTIONS.map(section => ( <Section key={section.title} {...section} /> ))} </ScrollView> </Block> </TitleRegistryProvider> ); }