Back to Dictionary
Scroll & Navigation
●○○Ready to Use
Recommended defaultSticky Heading
As the user scrolls down a list, the category header sticks to the top of the viewport until pushed out by the next category header.
Use
Alphabetical contact lists
Avoid
Short lists where context is never lost
Safer Alternative
No safer default listed.
Risk Level
Low risk
Live Demo
What It Is
As the user scrolls down a list, the category header sticks to the top of the viewport until pushed out by the next category header.
✓When to Use
- Alphabetical contact lists
- Long settings menus with grouped sections
- Documentation sidebars
✕When NOT to Use
- Short lists where context is never lost
Configuration Tips
- 01Use CSS position: sticky and top: 0
- 02Add a solid background color or backdrop-blur to the header so list items don't bleed through
You've Seen It In
iOS SettingsInstagram FeedNotion
AI Implementation Prompts
Move from a fast scaffold to production details, then tune timing and edge states.
Make these section headers stick to the top of the screen while scrolling through their respective lists.
Reference Implementation
The same implementation approach used by the live preview, kept compact enough to inspect and adapt.
"use client";
import { useRef, useEffect } from 'react';
export function StickyHeadingDemo({ isPlaying = false }: { isPlaying?: boolean }) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isPlaying || !containerRef.current) return;
let pos = 0;
let direction = 1;
const interval = setInterval(() => {
if (!containerRef.current) return;
pos += 1.5 * direction;
if (pos > 500) direction = -1;
if (pos <= 0) direction = 1;
containerRef.current.scrollTop = pos;
}, 16);
return () => clearInterval(interval);
}, [isPlaying]);
const data = [
{ letter: 'A', items: ['Apple', 'Apricot', 'Avocado'] },
{ letter: 'B', items: ['Banana', 'Blackberry', 'Blueberry'] },
{ letter: 'C', items: ['Cherry', 'Coconut', 'Cranberry'] },
];
return (
<div className="flex h-64 w-full items-center justify-center overflow-hidden rounded-2xl bg-white p-4 shadow-sm ring-1 ring-stone-200">
<div
ref={containerRef}
className="w-full max-w-[280px] h-56 overflow-y-scroll rounded-xl border border-stone-200 bg-stone-50 hide-scrollbar shadow-inner relative"
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
>
{data.map((group) => (
<div key={group.letter} className="relative pb-4">
<div className="sticky top-0 bg-stone-100/90 backdrop-blur px-4 py-1.5 border-y border-stone-200 z-10 font-bold text-stone-700 text-sm">
{group.letter}
</div>
<div className="px-4 py-2 flex flex-col gap-2 mt-2">
{group.items.map(item => (
<div key={item} className="p-3 bg-white rounded-lg shadow-sm text-sm text-stone-600 border border-stone-100 flex items-center gap-3">
<div className="w-6 h-6 rounded-full bg-stone-100 flex items-center justify-center text-[10px] font-bold text-stone-400">
{item[0]}
</div>
{item}
</div>
))}
</div>
</div>
))}
</div>
</div>
);
}