HextaUI

Message scroller: Jumping to messages

useMessageScroller drives the thread from outside it, and useMessageScrollerVisibility reports the current turn, so the outline highlights where you are.

How do I install the component?
Here is how to install the component. The outline on the side highlights this question while it is the current turn, and clicking another one jumps straight to it.
How do I anchor each turn?
Here is how to anchor each turn. The outline on the side highlights this question while it is the current turn, and clicking another one jumps straight to it.
How do I follow streamed replies?
Here is how to follow streamed replies. The outline on the side highlights this question while it is the current turn, and clicking another one jumps straight to it.
How do I load earlier messages?
Here is how to load earlier messages. The outline on the side highlights this question while it is the current turn, and clicking another one jumps straight to it.
How do I jump around the thread?
Here is how to jump around the thread. The outline on the side highlights this question while it is the current turn, and clicking another one jumps straight to it.
"use client"

import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Item, ItemContent, ItemGroup, ItemTitle } from "@/components/ui/item"
import { Message, MessageContent } from "@/components/ui/message"
import {
  MessageScroller,
  MessageScrollerContent,
  MessageScrollerItem,
  MessageScrollerProvider,
  MessageScrollerViewport,
  useMessageScroller,
  useMessageScrollerVisibility,
} from "@/components/ui/message-scroller"

const topics = [
  "Install the component",
  "Anchor each turn",
  "Follow streamed replies",
  "Load earlier messages",
  "Jump around the thread",
]

function Outline() {
  const { scrollToMessage } = useMessageScroller()
  const { currentAnchorId } = useMessageScrollerVisibility()

  return (
    <ItemGroup variant="grouped" className="w-44 shrink-0">
      {topics.map((topic, index) => (
        <Item
          key={topic}
          size="xs"
          render={<button type="button" />}
          aria-current={
            currentAnchorId === `topic-${index}` ? "true" : undefined
          }
          aria-pressed={currentAnchorId === `topic-${index}`}
          onClick={() =>
            scrollToMessage(`topic-${index}`, { behavior: "smooth" })
          }
        >
          <ItemContent>
            <ItemTitle>{topic}</ItemTitle>
          </ItemContent>
        </Item>
      ))}
    </ItemGroup>
  )
}

export function MessageScrollerJump() {
  return (
    <MessageScrollerProvider defaultScrollPosition="start">
      <div className="flex w-full max-w-xl items-start gap-4">
        <Outline />
        <div className="h-80 min-w-0 flex-1 overflow-hidden rounded-xl border">
          <MessageScroller>
            <MessageScrollerViewport aria-label="Guide">
              <MessageScrollerContent>
                {topics.map((topic, index) => [
                  <MessageScrollerItem
                    key={`q${index}`}
                    messageId={`topic-${index}`}
                    scrollAnchor
                  >
                    <Message align="end">
                      <MessageContent>
                        <Bubble variant="secondary">
                          <BubbleContent>
                            How do I {topic.toLowerCase()}?
                          </BubbleContent>
                        </Bubble>
                      </MessageContent>
                    </Message>
                  </MessageScrollerItem>,
                  <MessageScrollerItem
                    key={`a${index}`}
                    messageId={`a${index}`}
                  >
                    <Message>
                      <MessageContent>
                        <Bubble variant="ghost">
                          <BubbleContent>
                            Here is how to {topic.toLowerCase()}. The outline on
                            the side highlights this question while it is the
                            current turn, and clicking another one jumps
                            straight to it.
                          </BubbleContent>
                        </Bubble>
                      </MessageContent>
                    </Message>
                  </MessageScrollerItem>,
                ])}
              </MessageScrollerContent>
            </MessageScrollerViewport>
          </MessageScroller>
        </div>
      </div>
    </MessageScrollerProvider>
  )
}

This adds the example as components/examples/message-scroller/jump.tsx, together with the Message scroller component and the HextaUI theme tokens it needs.

pnpm dlx shadcn@latest add https://hextaui.com/r/message-scroller-jump.json

To add only the component, follow the Message scroller installation.

Props in this example

The Message scroller props this example sets, from the API reference.

PropTypeDefault
defaultScrollPositionWhere the thread opens.
"start" | "end" | "last-anchor""end"
PropTypeDefault
aria-labelNames the conversation region.
string–
PropTypeDefault
messageIdStable id for following, anchors and counts.
string–
scrollAnchorScroll this item to the top when it arrives, with defaultScrollPosition="last-anchor".
booleanfalse

Every prop is in the Message scroller API reference.

See them all, with keyboard support and accessibility notes, on the Message scroller page.