primitives について
primitivesは、カスタムチャットUIを構築する開発者向けの、assistant-uiのスタイルなしで組み合わせ可能なReactコンポーネントです。スレッド、メッセージ、入力欄の動作を備え、asChildで独自要素に適用できます。
主な機能
- 01スレッドとメッセージの組み合わせ部品
- 02入力、送信、キャンセル、添付ファイル処理
- 03自動スクロールと実行状態の管理
- 04分岐切替、編集、メッセージ操作
- 05キーボードショートカットとフォーカス管理
活用シーン
- 01カスタムのチャットスレッド画面を構築する
- 02メッセージに編集、分岐、操作バーを加える
- 03添付ファイルや引用に対応する入力欄を作る
- 04実行状態に応じてUI表示を切り替える
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ assistant-ui/ skills --skill primitivesインストール後、Claude Code で/primitivesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/action-bar.mdcomposer-input.mdcomposer.mdmentions.mdmessage.mdmessages.mdpart-grouping.mdsuggestions.mdthread.md詳細
assistant-ui Primitives
最新の API については、常に assistant-ui.com/llms.txt を参照してください。
Primitives は、Radix スタイルのパート構成に従った、組み合わせ可能でスタイルのないコンポーネントです。.Root がコンテキストを提供し、.PartName の子要素がそれを読み取り、すべてのパートは asChild を受け付けて、ラッパーをレンダリングする代わりに自身の動作を独自の要素にマージできます。スタイルやレイアウトに関する意見は一切含まれず、動作のみを提供します。すなわち、キーボードショートカット、自動スクロール、ストリーミング状態、フォーカス管理、無効化ロジックです。いずれかを使用する前に、ツリーを AssistantRuntimeProvider(またはネストされた AuiProvider)でラップしてください。
リファレンス
- ./references/thread.md -- ThreadPrimitive: ビューポート、自動スクロール、ターンアンカー、メッセージイテレーター
- ./references/composer.md -- ComposerPrimitive: ルート、入力、送信、キャンセル、送信動作
- ./references/message.md -- MessagePrimitive: パーツパイプライン、ツール解決、添付ファイル、引用、エラー
- ./references/action-bar.md -- ActionBarPrimitive と ActionBarMorePrimitive のオーバーフローメニュー
- ./references/part-grouping.md -- GroupedParts、groupPartByType、思考プロセス UI
- ./references/mentions.md --
@メンション、/スラッシュコマンド、カスタムトリガーマッチャー - ./references/composer-input.md -- ヘッドレス入力、入力履歴、ディクテーション、引用、添付ファイル
- ./references/messages.md -- 編集、分岐、メッセージのタイミング、仮想化、スクロールバー、画像パーツ
- ./references/suggestions.md --
Suggestions([...])、サジェスチョンアダプター、ThreadPrimitive.Suggestions
インポート
import {
AuiIf,
ThreadPrimitive,
ComposerPrimitive,
MessagePrimitive,
MessagePartPrimitive,
ActionBarPrimitive,
ActionBarMorePrimitive,
BranchPickerPrimitive,
AttachmentPrimitive,
ErrorPrimitive,
AssistantModalPrimitive,
ChainOfThoughtPrimitive,
SelectionToolbarPrimitive,
SuggestionPrimitive,
QueueItemPrimitive,
ThreadListPrimitive,
ThreadListItemPrimitive,
ThreadListItemMorePrimitive,
} from "@assistant-ui/react";
プリミティブパーツ
| プリミティブ | パーツ |
|---|---|
ThreadPrimitive | .Root, .Viewport, .ViewportProvider, .ViewportFooter, .Messages, .MessageByIndex, .Unstable_MessageById, .ScrollToBottom, .Suggestions, .SuggestionByIndex, .Suggestion, .Empty (非推奨), .If (非推奨) |
ComposerPrimitive | .Root, .Input, .Send, .Cancel, .AddAttachment, .Attachments, .AttachmentByIndex, .AttachmentDropzone, .Dictate, .StopDictation, .DictationTranscript, .Quote, .QuoteText, .QuoteDismiss, .Queue, .Unstable_TriggerPopoverRoot, .Unstable_TriggerPopover (.Directive / .Action 付き), .Unstable_TriggerPopoverCategories, .Unstable_TriggerPopoverCategoryItem, .Unstable_TriggerPopoverItems, .Unstable_TriggerPopoverItem, .Unstable_TriggerPopoverBack, .If (非推奨) |
MessagePrimitive | .Root, .Parts (.Content は非推奨のエイリアス), .PartByIndex, .GroupedParts, .Unstable_PartsGrouped, .Unstable_PartsGroupedByParentId, .Attachments, .AttachmentByIndex, .Quote, .Error, .GenerativeUI, .If (非推奨) |
MessagePartPrimitive | .Text, .Image, .InProgress, .Messages |
ActionBarPrimitive | .Root, .Copy, .Reload, .Edit, .Speak, .StopSpeaking, .FeedbackPositive, .FeedbackNegative, .ExportMarkdown |
ActionBarMorePrimitive | .Root, .Trigger, .Content, .Item, .Separator |
BranchPickerPrimitive | .Root, .Previous, .Next, .Number, .Count |
AttachmentPrimitive | .Root, .Name, .Remove, .unstable_Thumb |
ErrorPrimitive | .Root, .Message |
AssistantModalPrimitive | .Root, .Trigger, .Content, .Anchor |
ChainOfThoughtPrimitive | .Root, .AccordionTrigger, .Parts (レガシー、MessagePrimitive.GroupedParts を推奨) |
SelectionToolbarPrimitive | .Root, .Quote |
SuggestionPrimitive | .Title, .Description, .Trigger |
QueueItemPrimitive | .Text, .Steer, .Remove |
ThreadListPrimitive | .Root, .New, .Items, .ItemByIndex, .LoadMore |
ThreadListItemPrimitive | .Root, .Trigger, .Title, .Archive, .Unarchive, .Delete |
ThreadListItemMorePrimitive | .Root, .Trigger, .Content, .Item, .Separator |
ThreadListPrimitive、ThreadListItemPrimitive、ThreadListItemMorePrimitive はマルチスレッドのサイドバーを制御します。このスキルでは上記の表でのみ扱います。カスタム UI の全体像、CRUD 操作、リモートアダプターのチュートリアルについては thread-list を参照してください。
AuiIf による条件付きレンダリング
新しい条件にはすべて AuiIf を使用してください。固定のブール値プロパティの代わりに、アシスタントの状態全体 (thread、message、composer、part、attachment) に対するセレクターを受け取り、ThreadPrimitive、MessagePrimitive、ComposerPrimitive にまだ同梱されている非推奨の .If を置き換えます。
<AuiIf condition={(s) => s.thread.isEmpty}>
<WelcomeScreen />
</AuiIf>
<AuiIf condition={(s) => s.thread.isRunning}>
<ComposerPrimitive.Cancel>Stop</ComposerPrimitive.Cancel>
</AuiIf>
<AuiIf
condition={(s) => s.message.role === "assistant" && s.message.status?.type === "complete"}
>
<FollowUpCard />
</AuiIf>
<AuiIf condition={(s) => s.composer.dictation != null}>
<ComposerPrimitive.StopDictation>Stop</ComposerPrimitive.StopDictation>
</AuiIf>
AuiIf.Condition は、JSX の外部で条件関数に型を付けるためにエクスポートされています。s.message と s.part はメッセージまたはパーツのコンテキスト内でのみ値が入ります。s.thread と s.composer は常に利用できます。
カスタムスレッドの例
プリミティブから構築された完全なスレッド: ウェルカムサジェスチョン、インライン編集コンポーザー付きのロールごとのメッセージ、アクションバー、ブランチピッカー、そして実行中に Send を Cancel に切り替えるコンポーザーフッターです。
function CustomThread() {
return (
<ThreadPrimitive.Root className="flex h-full flex-col">
<ThreadPrimitive.Viewport className="flex flex-1 flex-col gap-4 overflow-y-auto p-4">
<AuiIf condition={(s) => s.thread.isEmpty}>
<div className="flex flex-1 flex-col items-center justify-center gap-3">
<p>Ask me anything.</p>
<ThreadPrimitive.Suggestions>
{() => (
<SuggestionPrimitive.Trigger send className="rounded-lg border px-3 py-2">
<SuggestionPrimitive.Title />
</SuggestionPrimitive.Trigger>
)}
</ThreadPrimitive.Suggestions>
</div>
</AuiIf>
<ThreadPrimitive.Messages>
{({ message }) => {
if (message.role === "user") {
return message.composer.isEditing ? <EditComposer /> : <UserMessage />;
}
return <AssistantMessage />;
}}
</ThreadPrimitive.Messages>
<ThreadPrimitive.ViewportFooter className="sticky bottom-0 pt-2">
<ComposerPrimitive.Root className="flex items-end gap-2 rounded-2xl border bg-background p-2">
<ComposerPrimitive.Input
placeholder="Send a message..."
rows={1}
className="flex-1 resize-none bg-transparent px-2 py-1.5 focus:outline-none"
/>
<AuiIf condition={(s) => !s.thread.isRunning}>
<ComposerPrimitive.Send className="rounded-full bg-primary px-3 py-1.5 text-primary-foreground" />
</AuiIf>
<AuiIf condition={(s) => s.thread.isRunning}>
<ComposerPrimitive.Cancel className="rounded-full border px-3 py-1.5" />
</AuiIf>
</ComposerPrimitive.Root>
</ThreadPrimitive.ViewportFooter>
</ThreadPrimitive.Viewport>
</ThreadPrimitive.Root>
);
}
function UserMessage() {
return (
<MessagePrimitive.Root className="flex flex-col items-end gap-1">
<MessagePrimitive.Quote>
{({ text }) => <blockquote className="border-l pl-2 text-sm italic">{text}</blockquote>}
</MessagePrimitive.Quote>
<div className="max-w-[80%] rounded-2xl bg-primary px-4 py-2 text-primary-foreground">
<MessagePrimitive.Parts>
{({ part }) => {
if (part.type === "text") return <MessagePartPrimitive.Text />;
if (part.type === "image") return <MessagePartPrimitive.Image className="max-w-full rounded-lg" />;
return null;
}}
</MessagePrimitive.Parts>
</div>
<ActionBarPrimitive.Root hideWhenRunning autohide="not-last">
<ActionBarPrimitive.Edit>Edit</ActionBarPrimitive.Edit>
</ActionBarPrimitive.Root>
</MessagePrimitive.Root>
);
}
function EditComposer() {
return (
<MessagePrimitive.Root className="flex justify-end">
<ComposerPrimitive.Root className="w-[80%] rounded-2xl border p-2">
<ComposerPrimitive.Input className="w-full resize-none bg-transparent focus:outline-none" />
<div className="flex justify-end gap-2 pt-1">
<ComposerPrimitive.Cancel className="rounded-md px-2 py-1 text-sm">Cancel</ComposerPrimitive.Cancel>
<ComposerPrimitive.Send className="rounded-md bg-primary px-2 py-1 text-sm text-primary-foreground">
Save
</ComposerPrimitive.Send>
</div>
</ComposerPrimitive.Root>
</MessagePrimitive.Root>
);
}
function AssistantMessage() {
return (
<MessagePrimitive.Root className="flex flex-col items-start gap-1">
<div className="max-w-[80%] rounded-2xl bg-muted px-4 py-2">
<MessagePrimitive.Parts>
{({ part }) => {
switch (part.type) {
case "text":
return <p className="whitespace-pre-wrap"><MessagePartPrimitive.Text /></p>;
case "image":
return <MessagePartPrimitive.Image className="max-w-full rounded-lg" />;
case "file":
return (
<a href={part.data ?? part.url} download={part.filename} className="text-sm underline">
{part.filename ?? part.mimeType}
</a>
);
case "reasoning":
return (
<details className="text-sm text-muted-foreground">
<summary>Thinking</summary>
{part.text}
</details>
);
case "source":
return part.sourceType === "url" ? (
<a href={part.url} className="text-sm underline">{part.title ?? part.url}</a>
) : (
<span className="text-sm">{part.title}</span>
);
case "tool-call":
return part.toolUI ?? <div className="rounded-md border p-2 text-sm">{part.toolName}</div>;
case "data":
return part.dataRendererUI ?? null;
case "generative-ui":
return <MessagePrimitive.GenerativeUI />;
default:
return null; // registered tool and data UIs still render when you return null
}
}}
</MessagePrimitive.Parts>
</div>
<MessagePrimitive.Error>
<ErrorPrimitive.Root className="text-sm text-destructive">
<ErrorPrimitive.Message />
</ErrorPrimitive.Root>
</MessagePrimitive.Error>
<div className="flex items-center gap-2">
<BranchPickerPrimitive.Root hideWhenSingleBranch className="flex items-center gap-1 text-xs">
<BranchPickerPrimitive.Previous>←</BranchPickerPrimitive.Previous>
<span><BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count /></span>
<BranchPickerPrimitive.Next>→</BranchPickerPrimitive.Next>
</BranchPickerPrimitive.Root>
<ActionBarPrimitive.Root hideWhenRunning autohide="not-last">
<ActionBarPrimitive.Copy>Copy</ActionBarPrimitive.Copy>
<ActionBarPrimitive.Reload>Regenerate</ActionBarPrimitive.Reload>
</ActionBarPrimitive.Root>
</div>
</MessagePrimitive.Root>
);
}
上記のアシスタントメッセージの .Parts コールバックは、パイプラインが渡す可能性のある 8 種類すべてのパーツを処理します: 3 つのモダリティパーツ (text、image、file)、4 つのプロバイダーチャネルパーツ (reasoning、source、tool-call、generative-ui)、そしてオープンエンドな data パーツです。default ケースから null を返した場合でも、名前で登録されたツール UI や name で登録されたデータレンダラーが自動的に引き継ぐことができます。そのパーツを完全に非表示にしたい場合は、代わりに <></> を返してください。
ブランチピッカー
BranchPickerPrimitive は最も近い MessagePrimitive.Root からブランチの状態を読み取るため、その中でレンダリングする必要があります。Previous と Next は境界で自動的に無効化されます (また、ランタイムが switchBranchDuringRun をサポートしていない限り、実行中も無効化されます)。hideWhenSingleBranch は、メッセージに実際に代替ブランチができるまでピッカー全体を非表示にするため、ブランチが 1 つという一般的なケースでアクション行がガタつくのを防ぎます。
<BranchPickerPrimitive.Root hideWhenSingleBranch className="inline-flex items-center gap-1">
<BranchPickerPrimitive.Previous>←</BranchPickerPrimitive.Previous>
<span><BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count /></span>
<BranchPickerPrimitive.Next>→</BranchPickerPrimitive.Next>
</BranchPickerPrimitive.Root>
新しいブランチは、ユーザーメッセージが編集されて再送信されたとき、または ActionBarPrimitive.Reload (もしくは aui.message.reload()) がアシスタントメッセージを再生成したときに現れます。aui.message.switchToBranch を使ったプログラムによるブランチナビゲーションについては、messages.md を参照してください。
よくある落とし穴
プリミティブが何もレンダリングしない、またはプロバイダーの外でフックが例外をスローする
- すべてのプリミティブは最も近いランタイムコンテキストから読み取ります。ツリーを
AssistantRuntimeProvider runtime={...}(useChatRuntimeやuseLocalRuntimeなどのランタイムフックから取得) でラップするか、分離されたスコープのためにAuiProvider extends={aui} config={AuiConfig({...})}をネストしてください。 ActionBarPrimitive、BranchPickerPrimitive、ErrorPrimitiveはさらにMessagePrimitive.Rootの祖先が必要です。SelectionToolbarPrimitiveはThreadPrimitive.Rootの祖先が必要ですが、ThreadPrimitive.Viewportの外側に配置する必要があります。
何もスタイルが適用されない
- プリミティブはクラスのない素のネイティブ要素 (
<div>、<button>、<textarea>など) をレンダリングします。自分でclassNameを追加するか、asChildを渡して、すでに持っているスタイル付きコンポーネントに動作をマージしてください。
ThreadPrimitive.ViewportSlack はもう存在しない
- これはパブリック API から削除されました。トップアンカーのターゲット登録は、
ViewportがturnAnchor="top"を持つ場合にMessagePrimitive.Rootの内部で自動的に行われるようになりました。fillClampThreshold/fillClampOffsetのカスタマイズは、代わりにViewportのtopAnchorMessageClampに置き換えてください。
MessagePrimitive.Content、.Empty、およびすべての .If はまだ動作するがレガシー
.Contentは.Partsの非推奨エイリアスです。ThreadPrimitive.Emptyとすべてのプリミティブの.IfはAuiIfに取って代わられました。まだ移行していないコードでのみ残しておいてください。
groupPartByType({ "mcp-app": [...] }) が黙ってマッチしなくなる
"mcp-app"キーは 0.15 で削除されました。代わりに"standalone-tool-call"を使用してください。これは、登録された UI がdisplay: "standalone"をオプトインしているすべてのツール呼び出しにもマッチするスーパーセットです。詳細は part-grouping.md を参照してください。
Unstable_ プレフィックス付きの props、フック、プリミティブ
- コンポーザートリガーポップオーバーファミリー、
unstable_useComposerInput、unstable_useComposerInputHistory、unstable_useThreadMessageIdsは明示的に不安定であり、メジャーバージョンのアップなしに変更される可能性があります。これらを使って構築しても安全ですが、アップグレードする前に依存している正確な動作を固定してください。
関連スキル
- elements -- これらのプリミティブから構築された、プロジェクトにコピーできるスタイル付きコンポーネント
- setup --
create、init、add、およびその他の CLI スキャフォールド - runtime --
useAui、useAuiState、AuiConfig、およびすべてのプリミティブが読み取る状態 - tools --
MessagePrimitive.Parts内でpart.toolUIに解決されるツールキットのrenderエントリ - thread-list --
ThreadListPrimitiveとThreadListItemPrimitiveの詳細