Components / Scroll

Sticky

Sticky is a Box that pins to its scroll container's edge. It scrolls with the content until it reaches the offset you set, then holds there while the rest of the content moves past.

Loading playground…
Updated 3 days agoEdit on GitHubWeb & native

What it is

A <Box position="sticky"> with a pin offset. By default Sticky pins to the top of its scroll context (top: 0); pass bottom instead for a footer-style sticky. The zIndex prop keeps the pinned element above the content that scrolls under it.

Sticky needs a scrolling ancestor to pin against — a ScrollView or any overflow: auto container. With no such ancestor, the element scrolls normally and the pin never engages.

Install

Sticky is exported from usemotif. No separate install.

example.tsx
import { Sticky } from 'usemotif';

API

Sticky

stable
function Sticky(props: StickyProps): JSX.Element
topnumber | string= 0

Pin offset from the top of the scroll container.

bottomnumber | string

Pin offset from the bottom. Pass this instead of `top` for a footer-style sticky.

zIndexnumber= 1

Stacking order — keeps the pinned element above the content scrolling beneath it.

…BoxPropsBoxProps

Every Box prop. `position` is managed by Sticky.

Examples

A sticky section header inside a scroll region:

example.tsx
<ScrollView h={400}>
<Sticky top={0} bg="$colors.surface.base">
  <Heading level={3}>Inbox</Heading>
</Sticky>
{messages.map((m) => <Message key={m.id} {...m} />)}
</ScrollView>

A sticky footer with a custom offset:

example.tsx
<Sticky bottom={16} zIndex={10}>
<Button fullWidth>Save changes</Button>
</Sticky>

Cross-platform notes

On web Sticky uses CSS position: sticky, supported in every modern browser. On native position: sticky is not part of the platform; the equivalent is a sticky-header prop on the underlying scroll component. Treat Sticky as a web-first primitive — on native, reach for the scroll component's own sticky-header support.