Message Scroller
An auto-scrolling viewport for chat messages, with a scroll-to-latest button. Built on @shadcn/react.
Last updated August 8, 2026
CreditsModifiedPublished
Copied from shadcn/ui.
- Dropped upstream's scroll-fade-b/scrollbar-thin/scrollbar-gutter-stable custom Tailwind utilities (not available in this repo) in favor of plain overflow-y-auto
- Replaced the multi-icon-library IconPlaceholder abstraction with a plain lucide-react ArrowDownIcon
- Replaced physical inset positioning with logical start-1/2
Overview
Scroll up inside the viewport below, then use the button to jump back to the latest message.
"use client"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import {
MessageScroller,
MessageScrollerButton,
MessageScrollerContent,
MessageScrollerItem,
MessageScrollerProvider,
MessageScrollerViewport,
} from "@/components/ui/message-scroller"
const messages = [
{ id: "1", align: "start" as const, text: "Hey! Are you around?" },
{ id: "2", align: "end" as const, text: "Yep, what's up?" },
{ id: "3", align: "start" as const, text: "Can you review my PR?" },
{ id: "4", align: "end" as const, text: "Sure, sending feedback now." },
{ id: "5", align: "start" as const, text: "Thanks, appreciate it!" },
]
export function MessageScrollerDemoExample() {
return (
<MessageScrollerProvider>
<MessageScroller className="h-56 w-full max-w-sm rounded-xl border border-border">
<MessageScrollerViewport>
<MessageScrollerContent className="p-4">
{messages.map((message) => (
<MessageScrollerItem key={message.id}>
<Bubble align={message.align}>
<BubbleContent>{message.text}</BubbleContent>
</Bubble>
</MessageScrollerItem>
))}
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton />
</MessageScroller>
</MessageScrollerProvider>
)
}Installation
$ npx shadcn@latest add https://ui.persian-labs.ir/r/message-scroller.jsonUsage
import {
MessageScroller,
MessageScrollerButton,
MessageScrollerContent,
MessageScrollerItem,
MessageScrollerProvider,
MessageScrollerViewport,
} from "@/components/ui/message-scroller"
export function Example() {
return (
<MessageScrollerProvider>
<MessageScroller className="h-56">
<MessageScrollerViewport>
<MessageScrollerContent>
{messages.map((message) => (
<MessageScrollerItem key={message.id}>
{message.text}
</MessageScrollerItem>
))}
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton />
</MessageScroller>
</MessageScrollerProvider>
)
}RTL
"use client"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import {
MessageScroller,
MessageScrollerButton,
MessageScrollerContent,
MessageScrollerItem,
MessageScrollerProvider,
MessageScrollerViewport,
} from "@/components/ui/message-scroller"
const messages = [
{ id: "1", align: "start" as const, text: "سلام! هستی؟" },
{ id: "2", align: "end" as const, text: "بله، چیکار داری؟" },
{ id: "3", align: "start" as const, text: "میشه PR من رو بررسی کنی؟" },
{ id: "4", align: "end" as const, text: "حتماً، الان بازخورد میفرستم." },
{ id: "5", align: "start" as const, text: "ممنون، لطف کردی!" },
]
export function MessageScrollerRtlExample() {
return (
<MessageScrollerProvider>
<MessageScroller className="h-56 w-full max-w-sm rounded-xl border border-border">
<MessageScrollerViewport>
<MessageScrollerContent className="p-4">
{messages.map((message) => (
<MessageScrollerItem key={message.id}>
<Bubble align={message.align}>
<BubbleContent>{message.text}</BubbleContent>
</Bubble>
</MessageScrollerItem>
))}
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton />
</MessageScroller>
</MessageScrollerProvider>
)
}