Sticky List Section
The StickyListSection component provides sticky section headers that pin to the top of a scroll container while the section is visible, then push out as the next section scrolls in. Built with IntersectionObserver, it detects when a header becomes stuck and applies a subtle shadow.
Variations
Contained mode
Use contained when the StickyListSection is nested inside intermediate wrapper elements (form inputs, layout stacks, cards) but the drawer body is still the scroll container. This mode uses --cn-sticky-container-padding to bleed the header out to the drawer edges through the wrapper layers.
Installation
import { StickyListSection } from '@harnessio/ui/components'
// Direct usage inside a drawer body<StickyListSection.Root> <StickyListSection.Header> <div>Section Title</div> </StickyListSection.Header> <StickyListSection.Content> {/* Scrollable list content */} </StickyListSection.Content></StickyListSection.Root>
// Contained mode — inside form wrappers<div style={{ '--cn-sticky-container-padding': '...' }}> <StickyListSection.Root contained> <StickyListSection.Header> <div>Section Title</div> </StickyListSection.Header> <StickyListSection.Content> {/* List content */} </StickyListSection.Content> </StickyListSection.Root></div>Anatomy
<StickyListSection.Root> {/* Container with sentinel for stuck detection */} <StickyListSection.Header> {/* Sticky header — gains shadow when stuck */} {children} </StickyListSection.Header> <StickyListSection.Content> {/* Section content */} {children} </StickyListSection.Content></StickyListSection.Root>StickyListSection.Rootwraps the section and renders an invisible sentinel just above the header. AnIntersectionObserverwatches that sentinel; when it scrolls out of view the header is “stuck”, andRootexposes that state to the header (also reflected asdata-stuck).StickyListSection.Headeris the pinned header. It picks up a shadow while stuck. Put the section title and any actions here.StickyListSection.Contentholds the scrollable body of the section.
All three parts forward their ref and accept standard <div> attributes.
Best practices
Do
- Keep the header content compact — a title and at most one or two actions — since it stays on screen while scrolling.
- Use it inside a scroll container (typically a
Drawer.Body) where sticky positioning has something to pin against. - Reach for
containedonly when wrapper elements sit between the section and the scroll container and you need the header to bleed to the edges.
Don’t
- Don’t nest a
StickyListSectioninside its own scrolling child — the header pins to the nearest scroll container, not the page. - Don’t rely on the stuck shadow alone to separate sections; keep clear header text for each.
Accessibility
Labeling
StickyListSectionrenders plain containers with no landmark or list roles, so each section’s meaning comes from the visible header content you provide. Give every section a clear text heading.
Focus & states
- The stuck state is a purely visual affordance (a shadow) and is not announced to assistive tech.
- The internal sentinel used for stuck detection is
aria-hiddenand never focusable.
Known limitations
- Sticky behavior depends on
IntersectionObserverand on the header having a scroll container to pin against; without one (or in environments without the observer) the header simply scrolls normally.
Properties
Root
The container component that manages stuck detection via IntersectionObserver.
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode | |
| contained | false | boolean | |
| className | false | string |
Root also accepts standard <div> attributes.
Header
The sticky header that pins to the top of the scroll container. Applies a shadow when stuck.
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode | |
| className | false | string |
Header also accepts standard <div> attributes.
Content
Wrapper for the section’s scrollable content.
Prop | Required | Default | Type |
|---|---|---|---|
| children | true | ReactNode | |
| className | false | string |
Content also accepts standard <div> attributes.