Skip to content
Mizen UI
Docs

IntroductionInstallationCLIStylingFoundationsThemingCompositionIntegrationsAccessibilityFor agents

useCopyToClipboard

AccordionAspect RatioCardCollapsibleFrameGridGroupInlineScroll AreaSectionSeparatorSpacerStack

BlockquoteCitationCodeHeadingKbdLabelLinkTextVisually Hidden

ButtonButton GroupToggleToggle groupToolbar

CalendarCheckboxCheckbox groupComboboxDate pickerDate range pickerDate-Time InputFieldFieldsetFile UploadFormInputInput groupNumber fieldOTP FieldRadio groupSelectSliderSwitchTextareaTime Input

BreadcrumbMenubarNavigation MenuPaginationSegmented ControlStepperTabs

Alert dialogCommandContext MenuDialogDrawerMenuPopoverPreview CardSheetTooltip

AlertBadgeEmptyError StateMeterProgressSkeletonSpinnerStatus dotToast

Activity FeedAvatarAvatar GroupData TableListMetricOverflow ListProperty ListTableTokenTree

Area ChartBar ListCalendar HeatmapCategory ComparisonChart FrameChart LegendChart TooltipDistributionFunnelHeatmapMetric TrendPart-to-WholeProgress RingsRadar ChartRadial BarsSankey ChartScatter ChartScore GaugeTime Series

Diagram

AttachmentCarouselCode BlockLightboxMarkdownThumbnailTimestamp

Agent ThinkingApproval CardComposerConversationInline CitationMessageMessage ScrollerQuestion CardSourcesStreaming TextSuggestionTask ListTerminalTool Call

On this page

  • Preview
  • Usage
  • Anatomy
  • With the AI SDK
  • With TanStack AI
  • Examples
  • Loading earlier messages
  • Recommendations
  • Usage boundaries
  • API reference

Agentic

Message Scroller

A scrolling transcript that opens at the end, follows new content while the reader is there, settles a sent turn near the top, and holds the reader's place when earlier messages load.

idle

Usage

import {
	MessageScroller,
	MessageScrollerButton,
	MessageScrollerContent,
	MessageScrollerItem,
	MessageScrollerViewport,
} from "@mizen-labs/ui";

Anatomy

The parts and how they nest, taken from the preview above.

<MessageScroller>
	<MessageScrollerViewport>
		<MessageScrollerContent>
			<MessageScrollerItem />
		</MessageScrollerContent>
	</MessageScrollerViewport>
	<MessageScrollerButton />
</MessageScroller>

With the AI SDK

Wrap each message in an item keyed by its id, and anchor the person's turns. Conversation does this for you.

import { useChat } from "@ai-sdk/react";
import {
	MessageScroller,
	MessageScrollerButton,
	MessageScrollerContent,
	MessageScrollerItem,
	MessageScrollerViewport,
} from "@mizen-labs/ui";

export function Transcript() {
	const { messages } = useChat();
	return (
		<MessageScroller>
			<MessageScrollerViewport>
				<MessageScrollerContent>
					{messages.map((message) => (
						<MessageScrollerItem
							key={message.id}
							messageId={message.id}
							scrollAnchor={message.role === "user"}
						>
							<Row message={message} />
						</MessageScrollerItem>
					))}
				</MessageScrollerContent>
			</MessageScrollerViewport>
			<MessageScrollerButton />
		</MessageScroller>
	);
}

With TanStack AI

The same items, keyed by the message id.

import { useChat } from "@tanstack/ai-react";
import { MessageScroller, MessageScrollerContent, MessageScrollerItem, MessageScrollerViewport } from "@mizen-labs/ui";

export function Transcript() {
	const { messages } = useChat();
	return (
		<MessageScroller>
			<MessageScrollerViewport>
				<MessageScrollerContent>
					{messages.map((message) => (
						<MessageScrollerItem key={message.id} messageId={message.id} scrollAnchor={message.role === "user"}>
							<Row message={message} />
						</MessageScrollerItem>
					))}
				</MessageScrollerContent>
			</MessageScrollerViewport>
		</MessageScroller>
	);
}

Examples

Loading earlier messages

Earlier messages load above the ones in view, and the reader's place does not move.

Recommendations

GuidancePractice
Do

Use Conversation for an agent thread; reach for Message Scroller for other transcripts, such as a team chat or a log. Anchor turns the person sends, and load earlier messages above without scrolling yourself.

Usage boundaries

GuidanceBoundary
Scope

Message Scroller handles scrolling only: it renders no messages and fetches no history.

Scope

Keep messageId stable across renders; an index shifts when earlier messages load and breaks the reader's place.

API reference

Public props from @mizen-labs/ui. Use exported prop types for the complete TypeScript contract.

PropTypeDefaultDescription
MessageScroller{ children }—The frame. It fills a parent with a bounded height, opens at the end, and follows new content while the reader is there.
MessageScrollerViewport{ aria-label?, children }"Messages"The scrolling region, built on Scroll Area: a thin thumb shows on hover and while scrolling, and the edges fade where more waits. It is focusable so the keyboard can scroll it. Wheel, touch, and keys going back take over from following.
MessageScrollerContent{ busy?, children }—The transcript, a live log that announces new rows. Direct children are MessageScrollerItems, 24px apart. busy marks it while a reply streams.
MessageScrollerItem{ messageId, scrollAnchor?: boolean | "auto" }—One row. messageId is stable, for jumping and for holding the reader's place. scrollAnchor marks a turn the person sent: it settles near the top, 64px of the previous turn above it, and the view stays put while the reply grows. "auto" anchors the row when it holds the reader's own Message.
MessageScrollerButton{ aria-label? }"Jump to latest"A round arrow that fades in when there is something below the view, and leaves the tab order when there is not.
MessageScrollerNavigation{ aria-label? }"Turns"A rail of ticks along the end edge, one per anchored turn, from two turns. The tick in focus is longest and its neighbors step down. Hover or focus one to preview the turn and the start of its reply; press it to jump there. Keep the thread's end edge 40px clear.
useMessageScroller / useMessageScrollerStateHooks—scrollToEnd() and scrollToMessage(id) for custom controls; useMessageScrollerState() reads atEnd, the anchors, and the current one.
PreviousMessageNextQuestion Card
MC
Maya Chen

Pushed the container query change for Card.

AR
Alex Rivera

Pulling it now.

Nice. I'll check the settings sidebar.

JL
Jordan Lee

Visual tests are green on my side.

MC
Maya Chen

Pushed the container query change for Card.

AR
Alex Rivera

Visual tests are green on my side.

JL
Jordan Lee

The sidebar looks right at 390px now.

MC
Maya Chen

Can someone check the grid on a tablet?

AR
Alex Rivera

Grid lines up. One card has a long title that wraps to three lines.

JL
Jordan Lee

That one clamps at two lines in the design.