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>
);
}React Native design for iOS, Android, and Web.
Quick Links
Resources