Thread プリミティブはスクロール可能なメッセージコンテナで、あらゆるチャットインターフェイスのバックボーンとなります。
自動スクロール、空の状態、メッセージレンダリングを備えたスクロール可能なカスタム・メッセージコンテナを構築できます。
assistant-ui 入門 : プリミティブ – Composer
作成 : Masashi Okumura (@classcat.com)
作成日時 : 03/28/2026
バージョン : assistant-ui@0.0.85
* 本記事は assistant-ui.com/docs の以下のページを参考にしています :
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
assistant-ui 入門 : プリミティブ – Composer
自動スクロール、空の状態、メッセージレンダリングを備えたスクロール可能なカスタム・メッセージコンテナを構築できます。
Thread プリミティブはスクロール可能なメッセージコンテナで、あらゆるチャットインターフェイスのバックボーンとなります。ビューポート管理、自動スクロール、空の状態、メッセージレンダリング、提案 (suggestions) 機能を処理します。開発者はレイアウトとスタイリングを提供します。
Preview

Code
import {
AuiIf,
ComposerPrimitive,
ThreadPrimitive,
MessagePrimitive,
} from "@assistant-ui/react";
import { ArrowUpIcon } from "lucide-react";
function MinimalThread() {
return (
<ThreadPrimitive.Root className="flex h-full flex-col">
<ThreadPrimitive.Viewport className="flex flex-1 flex-col gap-3 overflow-y-auto p-3">
<AuiIf condition={(s) => s.thread.isEmpty}>
<p>Welcome! Ask a question to get started.</p>
</AuiIf>
<ThreadPrimitive.Messages>
{({ message }) => {
if (message.role === "user") return <UserMessage />;
return <AssistantMessage />;
}}
</ThreadPrimitive.Messages>
<ThreadPrimitive.ViewportFooter className="sticky bottom-0 pt-2">
<ComposerPrimitive.Root className="flex w-full flex-col rounded-3xl border bg-muted">
<ComposerPrimitive.Input
placeholder="Ask anything..."
className="min-h-10 w-full resize-none bg-transparent px-5 pt-3.5 pb-2.5 text-sm focus:outline-none"
rows={1}
/>
<div className="flex items-center justify-end px-2.5 pb-2.5">
<ComposerPrimitive.Send className="flex size-8 items-center justify-center rounded-full bg-primary text-primary-foreground disabled:opacity-30">
<ArrowUpIcon className="size-4" />
</ComposerPrimitive.Send>
</div>
</ComposerPrimitive.Root>
</ThreadPrimitive.ViewportFooter>
</ThreadPrimitive.Viewport>
</ThreadPrimitive.Root>
);
}
function UserMessage() {
return (
<MessagePrimitive.Root className="flex justify-end">
<div className="max-w-[80%] rounded-2xl bg-primary px-4 py-2.5 text-sm text-primary-foreground">
<MessagePrimitive.Parts />
</div>
</MessagePrimitive.Root>
);
}
function AssistantMessage() {
return (
<MessagePrimitive.Root className="flex justify-start">
<div className="max-w-[80%] rounded-2xl bg-muted px-4 py-2.5 text-sm">
<MessagePrimitive.Parts />
</div>
</MessagePrimitive.Root>
);
}
クイックスタート
最小限の例 :
import { ThreadPrimitive } from "@assistant-ui/react";
<ThreadPrimitive.Root>
<ThreadPrimitive.Viewport>
<ThreadPrimitive.Messages>
{() => <MyMessage />}
</ThreadPrimitive.Messages>
</ThreadPrimitive.Viewport>
</ThreadPrimitive.Root>
Root は <div> をレンダリングし、Viewport はスクロール可能な <div> をレンダリングし、Messages はスレッドのメッセージに対して反復処理をします。独自のスタイルとコンポーネントを追加してください; プリミティブが残りの処理を行います。
ℹ️ ランタイム・セットアップ: プリミティブはランタイム・コンテキストを必要とします。UI をランタイム (例えば useLocalRuntime(…)) を持つ AssistantRuntimeProvider でラップします。See Pick a Runtime.
基本概念
Viewport & Auto-Scroll
Viewport はスクロール可能なコンテナです。新しいコンテンツがストリーミングされると、ユーザが手動でスクロールアップしていない場合、自動的にボトムまでスクロールします。これを完全に無効にするには、autoScroll={false} を設定します。
<ThreadPrimitive.Viewport autoScroll={true}>
{/* messages */}
</ThreadPrimitive.Viewport>
Turn Anchor (基準点の切り替え)
デフォルトでは、新しいメッセージは最下部に表示され、スクロールダウンされます。turnAnchor=”top” で、ユーザーのメッセージがビューポートの上端にアンカーとして固定されます。これは、質問が上部に、レスポンスがその下に流れるという、現代的なリーディング体験を実現します。
<ThreadPrimitive.Viewport turnAnchor="top">
{/* messages */}
</ThreadPrimitive.Viewport>
これは shadcn Thread コンポーネントがデフォルトで使用するものです。
Viewport のスクロール・オプション
ThreadPrimitive.Viewport はイベント固有の 3 つのスクロール制御を持ちます :
- scrollToBottomOnRunStart (デフォルト true): thread.runStart が発生したときにスクロールします
- scrollToBottomOnInitialize (デフォルト true): thread.initialize が発生したときにスクロールします
- scrollToBottomOnThreadSwitch (デフォルト true): threadListItem.switchedTo が発生したときにスクロールします
これらは autoScroll と連携して動作します。autoScroll が省略された場合には、turnAnchor=”bottom” ならば true がデフォルト、turnAnchor=”top” ならば false がデフォルトになります。
<ThreadPrimitive.Viewport
turnAnchor="top"
autoScroll={false}
scrollToBottomOnRunStart={true}
scrollToBottomOnInitialize={false}
scrollToBottomOnThreadSwitch={true}
>
<ThreadPrimitive.Messages>
{({ message }) => {
if (message.role === "user") return <UserMessage />;
return <AssistantMessage />;
}}
</ThreadPrimitive.Messages>
</ThreadPrimitive.Viewport>
ViewportFooter
ViewportFooter はビューポートの最下部に固定され、その高さを登録して自動スクロールシステムが対応できるようにします。ここが composer を配置する場所です :
<ThreadPrimitive.Viewport>
<ThreadPrimitive.Messages>
{() => <MyMessage />}
</ThreadPrimitive.Messages>
<ThreadPrimitive.ViewportFooter className="sticky bottom-0">
<MyComposer />
</ThreadPrimitive.ViewportFooter>
</ThreadPrimitive.Viewport>
空の状態 (Empty State)
⚠️ ThreadPrimitive.Empty は deprecated です。代わりに AuiIf を使用してください。
<AuiIf condition={(s) => s.thread.isEmpty}>
<div className="flex flex-col items-center gap-2 text-center">
<h2>Welcome!</h2>
<p>How can I help you today?
</div>
</AuiIf>
メッセージ・イテレータ
Messages は children レンダリング関数を推奨するようになりました。現在のメッセージ状態を提供しますので、インラインで分岐できます :
<ThreadPrimitive.Messages>
{({ message }) => {
if (message.composer.isEditing) return ;
if (message.role === "user") return ;
return ;
}}
</ThreadPrimitive.Messages>
ℹ️ components is deprecated. Use the children render function instead.
提案 (Suggestions) イテレータ
提案は同じパターンに従います。カスタム提案 UI をレンダリングする場合、children レンダリング関数を推奨します :
<ThreadPrimitive.Suggestions>
{() => <MySuggestionButton />}
</ThreadPrimitive.Suggestions>
部品 (パーツ)
Root
スレッド・レイアウト用のトップレベルのコンテナ。asChild が設定されていない場合、<div> をレンダリングします。
<ThreadPrimitive.Root className="flex h-full flex-col">
<ThreadPrimitive.Viewport>
<ThreadPrimitive.Messages>
{() => <MyMessage />}
</ThreadPrimitive.Messages>
</ThreadPrimitive.Viewport>
</ThreadPrimitive.Root>
Viewport
自動スクロールの機能を備えた、スクロール可能な領域。asChild が設定されていない場合、<div> をレンダリングします。
<ThreadPrimitive.Viewport
turnAnchor="top"
autoScroll={false}
scrollToBottomOnRunStart={true}
>
<ThreadPrimitive.Messages>
{({ message }) => {
if (message.role === "user") return <UserMessage />;
return <AssistantMessage />;
}}
</ThreadPrimitive.Messages>
</ThreadPrimitive.Viewport>
ViewportFooter
ビューポートのスクロールシステムでその高さを登録するフッター・コンテナ。asChild が設定されていない場合、<div> をレンダリングします。
<ThreadPrimitive.ViewportFooter className="sticky bottom-0 pt-2">
<ComposerPrimitive.Root>...</ComposerPrimitive.Root>
</ThreadPrimitive.ViewportFooter>
ViewportProvider
スクロール可能な要素をレンダリングしないで、ビューポート・コンテキストを提供します。カスタム・スクロール・コンテナを使用する場合にこれを使用します。
<ThreadPrimitive.ViewportProvider>
<div className="flex-1 overflow-y-auto">
<ThreadPrimitive.Messages>
{() => <MyMessage />}
</ThreadPrimitive.Messages>
<ThreadPrimitive.ViewportFooter>
<MyComposer />
</ThreadPrimitive.ViewportFooter>
</div>
</ThreadPrimitive.ViewportProvider>
ViewportSlack
スクロールアンカーに turnAnchor=”top” を設定することで、min-height (最小の高さ) を設定します。Slot で child 要素をラップし、独自の DOM 要素をレンダリングしません。
<MessagePrimitive.Root>
<MessagePrimitive.Parts />
<ThreadPrimitive.ViewportSlack>
<div className="min-h-[40vh]" />
</ThreadPrimitive.ViewportSlack>
</MessagePrimitive.Root>
Messages
スレッド内の各メッセージに対して、ロールと編集状態により解決された、コンポーネントをレンダリングします。
<ThreadPrimitive.Messages>
{({ message }) => {
if (message.composer.isEditing) return <MyEditComposer />;
if (message.role === "user") return <MyUserMessage />;
return <MyAssistantMessage />;
}}
</ThreadPrimitive.Messages>
Suggestions
コンポーネントを通して提案プロンプトをレンダリングします。
<ThreadPrimitive.Suggestions>
{({ suggestion }) => <MySuggestionButton prompt={suggestion.prompt} />}
</ThreadPrimitive.Suggestions>
Suggestion
自己完結型の提案ボタン。asChild が設定されない限り、<button> 要素をレンダリングします。(Legacy — prefer Suggestions iterator.)
<ThreadPrimitive.Suggestion prompt="Write a blog post" send />
- prompt : string – 提案プロンプト。
- send? : boolean – true の場合、メッセージを自動的に送信します。
以上