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
  • Empty state
  • Navigation rail
  • Recommendations
  • Usage boundaries
  • API reference

Agentic

Conversation

The thread with the agent: turns that scroll and follow the reply, an empty state, the composer docked under them, and an optional rail to jump between questions.

idle

Usage

import {
	Conversation,
	ConversationContent,
	ConversationEmptyState,
	ConversationFooter,
} from "@mizen-labs/ui";

Anatomy

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

<Conversation>
	<ConversationContent>
		<ConversationEmptyState />
	</ConversationContent>
	<ConversationFooter />
</Conversation>

With the AI SDK

Key each turn by the message id. Mark the log busy while the reply streams.

import { useChat } from "@ai-sdk/react";
import {
	Composer,
	ComposerInput,
	Conversation,
	ConversationContent,
	ConversationFooter,
	getComposerText,
	Message,
	StreamingText,
} from "@mizen-labs/ui";

export function Chat() {
	const { messages, sendMessage, status } = useChat();
	const streaming = status === "submitted" || status === "streaming";
	return (
		<Conversation>
			<ConversationContent busy={streaming}>
				{messages.map((message) =>
					message.role === "user" ? (
						<Message from="user" key={message.id}>
							{textOf(message)}
						</Message>
					) : (
						<Message from="agent" key={message.id}>
							<StreamingText content={textOf(message)} status={streaming ? "streaming" : "complete"} />
						</Message>
					)
				)}
			</ConversationContent>
			<ConversationFooter>
				<Composer onSubmit={(value) => sendMessage({ text: getComposerText(value) })}>
					<ComposerInput aria-label="Message" />
				</Composer>
			</ConversationFooter>
		</Conversation>
	);
}

With TanStack AI

Same shape: one turn per message, keyed by its id.

import { useChat } from "@tanstack/ai-react";
import { Conversation, ConversationContent, Message, StreamingText } from "@mizen-labs/ui";

export function Thread() {
	const { isLoading, messages } = useChat();
	return (
		<Conversation>
			<ConversationContent busy={isLoading}>
				{messages.map((message) =>
					message.role === "user" ? (
						<Message from="user" key={message.id}>{textOf(message)}</Message>
					) : (
						<Message from="agent" key={message.id}>
							<StreamingText content={textOf(message)} status={isLoading ? "streaming" : "complete"} />
						</Message>
					)
				)}
			</ConversationContent>
		</Conversation>
	);
}

Examples

Empty state

Before the first turn, a start screen. A starter sends in one click, and the agent thinks, reads a file, and answers.

Navigation rail

A long thread loaded from history, each turn folded. A mark per question runs down the end edge: hover or focus one to preview it, and press it to jump there.

Recommendations

GuidancePractice
Do

Key each turn by its message id and pass the person's turns as Message from="user", so they settle near the top when sent. Set busy while a reply streams, and add navigation once threads get long.

Usage boundaries

GuidanceBoundary
Scope

The application sends messages and streams replies. Conversation lays out and scrolls the thread; it stores nothing.

Scope

Give it a parent with a bounded height, such as a full-height panel. It does not size the page.

API reference

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

PropTypeDefaultDescription
Conversation{ children }—Holds the thread and the footer, and fills a parent with a bounded height.
ConversationContent.childrenReactNode—The turns, each with a key that is its id, and an optional ConversationEmptyState shown before the first one. A Message from the person settles near the top when sent.
ConversationContent.busybooleanfalseMarks the log busy while a reply streams, so assistive tech waits for it.
ConversationContent.navigationbooleanfalseAdds a MessageScrollerNavigation rail, one tick per question, to preview and jump to.
ConversationEmptyState{ title, description?, children? }—The start screen before the first turn; Suggestions go in children.
ConversationFooter{ children }—Holds the composer under the thread, in the thread's 48rem column.
PreviousComposerNextInline Citation

What should we build?

The agent can read your code, run commands, and make changes.

What should we build?

The agent can read your code, run commands, and make changes.

Why does Card break in the sidebar?

Thought

Done. I checked card.styles.ts and made the change; the visual tests pass in light, dark, and at 390px.

Move it to container queries.

Thought

Done. I checked card.styles.ts and made the change; the visual tests pass in light, dark, and at 390px.

Which breakpoints did you keep?

Thought

Done. I checked card.styles.ts and made the change; the visual tests pass in light, dark, and at 390px.

Run the visual tests at 390px.

Thought

Done. I checked tests/visual.test.ts and made the change; the visual tests pass in light, dark, and at 390px.

Does the grid still line up?

Thought

Done. I checked grid.styles.ts and made the change; the visual tests pass in light, dark, and at 390px.

Add a story for the narrow card.

Thought

Done. I checked card.stories.tsx and made the change; the visual tests pass in light, dark, and at 390px.

Update the changelog draft.

Thought

Done. I checked CHANGELOG.md and made the change; the visual tests pass in light, dark, and at 390px.

Open a pull request.

Thought

Done. I checked pull_request_template.md and made the change; the visual tests pass in light, dark, and at 390px.