useStickToBottom React hook in TypeScript
v1.0.0Added in 1.0.0
A React hook that keeps a chat, a log or streaming output pinned to its bottom while it grows, and lets go when the reader scrolls up.
The demo plays a conversation whose answers stream in word by word, and the panel follows them down. Scroll up with the wheel, a finger or the keys while an answer is still arriving: following stops at once (isFollowing turns off), the text keeps growing below and a New messages button appears. Tap it, or scroll back to the end, and it follows again. Pause stops the script.
useStickToBottom is a Pro React hook in the React hooks and utilities category, written in TypeScript for React 19. It installs as source with npx wingo-ui@latest add use-stick-to-bottom, together with Motion presets.
Add to a project
Requirements: React 19, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add use-stick-to-bottomuseStickToBottom is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import useStickToBottom from @/hooks/use-stick-to-bottom.
"use client"
import { useStickToBottom } from "@/hooks/use-stick-to-bottom"
import { Button } from "@/components/ui/button"
export function Conversation({ messages }: { messages: { id: string; text: string }[] }) {
const { scrollRef, contentRef, isAtBottom, scrollToBottom } = useStickToBottom()
return (
<div className="relative h-96">
<div ref={scrollRef} className="h-full overflow-y-auto overscroll-contain">
<div ref={contentRef} className="flex flex-col gap-3 p-4">
{messages.map((m) => <p key={m.id}>{m.text}</p>)}
</div>
</div>
{!isAtBottom && (
<Button size="sm" radius="full" className="absolute bottom-3 left-1/2 -translate-x-1/2" onClick={() => scrollToBottom()}>
New messages
</Button>
)}
</div>
)
}API
enableddefaulttruebooleanuseStickToBottom(options?): follow new content while the reader is at the bottom. false never scrolls by itself (scrollToBottom still works).
thresholddefault72numberDistance in px from the bottom that still counts as at the bottom: isAtBottom, and where scrolling back down re-sticks.
initialdefault"instant""instant" | "smooth" | falseHow to reach the bottom when the scroller attaches (opening a conversation). false starts at the top and does not follow until the reader scrolls down.
resizedefault"smooth""smooth" | "instant"How to follow content that grows while stuck: smooth eases toward a bottom that is re-read every frame, so a streaming answer glides; instant jumps. Reduced motion always jumps.
returns{ scrollRef; contentRef; isAtBottom: boolean; isFollowing: boolean; scrollToBottom(behavior?): void; stopFollowing(): void }scrollRef goes on the overflow-y-auto element, contentRef on the element inside it that grows (defaults to the first child). isAtBottom and isFollowing re-render only when they flip: show a jump-to-latest button while !isAtBottom. scrollToBottom scrolls down and follows again (call it after sending a message); stopFollowing lets go without scrolling.
Examples
Live log tail
A new line every 700ms. The view follows the newest one; scroll up to read and a Jump to latest button appears.
Dependencies
Files
- hooks/use-stick-to-bottom.ts
FAQ
Is the useStickToBottom hook free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install useStickToBottom?
Run npx wingo-ui@latest add use-stick-to-bottom. It also adds the Wingo UI item it builds on (Motion presets). With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/use-stick-to-bottom.
See also
Used in
More React hooks and utilities
Articles using this component
- React Log Viewer: Stream 100,000 Lines With Regex Search
Component guides8 min read
- React Markdown Streaming Without Flicker or Scroll Jumps
Building UI with AI agents10 min read