- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Data Table
- Date Picker
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Native Select
- Navigation Menu
- Pagination
- Popover
- Progress
- Questionnaire
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
- Typography
MessageScroller は @shadcn/react パッケージのスタイルなしプリミティブとして提供されています。
このパッケージは、すべてのスクロール動作、ターンの固定、ストリーミング出力への追従、履歴読み込み時の読者位置の保持、
表示状態の追跡を管理し、独自のスタイルは描画しません。
レジストリの message-scroller.tsx コンポーネントは、その上に Tailwind クラスを追加する薄いラッパーです。
マークアップとスタイルを完全に制御したい場合や、レジストリを使わない場合は、パッケージを直接使用してください。
動作ガイドと実際に操作できる例については、 Message Scroller コンポーネントをご覧ください。
インストール#
pnpm add @shadcn/react
使い方#
import {
MessageScroller,
useMessageScroller,
} from "@shadcn/react/message-scroller"パッケージはフラットなコンポーネントではなく、名前空間オブジェクトをエクスポートします。 パーツと動作はスタイル付きコンポーネントと同じで、スタイルだけがありません。
<MessageScroller.Provider>
<MessageScroller.Root>
<MessageScroller.Viewport>
<MessageScroller.Content>
{messages.map((message) => (
<MessageScroller.Item
key={message.id}
messageId={message.id}
scrollAnchor={message.role === "user"}
>
{/* your message UI */}
</MessageScroller.Item>
))}
</MessageScroller.Content>
</MessageScroller.Viewport>
<MessageScroller.Button />
</MessageScroller.Root>
</MessageScroller.Provider>パーツ#
スタイル付きコンポーネントから移行する場合、フラットなパーツは次のように名前空間オブジェクトへ対応します。
| Styled component | Unstyled part |
|---|---|
MessageScrollerProvider | MessageScroller.Provider |
MessageScroller | MessageScroller.Root |
MessageScrollerViewport | MessageScroller.Viewport |
MessageScrollerContent | MessageScroller.Content |
MessageScrollerItem | MessageScroller.Item |
MessageScrollerButton | MessageScroller.Button |
フックも同じ方法でインポートし、MessageScroller.Provider から読み取るため同じように動作します。
import {
useMessageScroller,
useMessageScrollerScrollable,
useMessageScrollerVisibility,
} from "@shadcn/react/message-scroller"例#
独自のスタイルを使い、スクローラーを AI SDK に接続する完全な例を示します。
"use client"
import { useChat } from "@ai-sdk/react"
import { MessageScroller } from "@shadcn/react/message-scroller"
import { DefaultChatTransport } from "ai"
import { ChatInput } from "@/components/chat-input"
export function Chat() {
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({ api: "/api/chat" }),
})
return (
<div className="flex h-svh w-full flex-col">
<MessageScroller.Provider>
<MessageScroller.Root className="relative flex flex-1 flex-col overflow-hidden">
<MessageScroller.Viewport className="flex flex-1 flex-col overflow-y-auto">
<MessageScroller.Content className="flex flex-col gap-4 p-6 text-base">
{messages.map((message, index) => (
<MessageScroller.Item
key={message.id}
messageId={`message-${index}`}
scrollAnchor={message.role === "user"}
>
<div className="rounded-lg bg-muted p-4">
{message.parts.map((part, i) =>
part.type === "text" ? (
<span key={i}>{part.text}</span>
) : null
)}
</div>
</MessageScroller.Item>
))}
</MessageScroller.Content>
</MessageScroller.Viewport>
<MessageScroller.Button className="absolute bottom-2 left-1/2 z-10 -translate-x-1/2 rounded-full border bg-background px-3 py-1 text-sm font-medium inert:opacity-0">
最新へ移動
</MessageScroller.Button>
</MessageScroller.Root>
</MessageScroller.Provider>
<ChatInput onSend={sendMessage} disabled={status !== "ready"} />
</div>
)
}再読み込み時のちらつきを防ぐ#
スクロール コンテナは常に上部から開きます。HTML には scrollTop を設定する方法がないため、サーバーでレンダリングされたトランスクリプトでは最も古いメッセージが先に表示されます。JavaScript の実行後に defaultScrollPosition が表示位置を移動させるため、ジャンプが見えてしまいます。
defaultScrollPosition が "end" または "last-anchor" の場合、位置が適用されるまでビューポートに data-pending-scroll が付きます。ビューポートに id を付け、この属性がある間は非表示にします。ルートまで隠さないよう、ルールはその id に限定してください。
#messages[data-pending-scroll] {
visibility: hidden;
}初回描画から "end" を表示したい場合は、ビューポートの直後にインライン スクリプトを追加します。同じ id を使って末尾へスクロールし、data-pending-scroll を削除します。
const scrollToEndScript = `(function () {
var viewport = document.getElementById("messages")
if (!viewport) {
return
}
viewport.scrollTop = viewport.scrollHeight
viewport.removeAttribute("data-pending-scroll")
})()`
<MessageScroller.Root>
<MessageScroller.Viewport id="messages" suppressHydrationWarning>
<MessageScroller.Content>{/* transcript */}</MessageScroller.Content>
</MessageScroller.Viewport>
<script dangerouslySetInnerHTML={{ __html: scrollToEndScript }} />
<MessageScroller.Button />
</MessageScroller.Root>スクリプトはプリミティブではなくページに配置します。これは "end" の場合だけ、かつメッセージがすでに HTML にある場合だけ動作します。ビューポートに suppressHydrationWarning を追加してください。Content Security Policy を使う場合は nonce を渡します。
このスクリプトを "last-anchor" と組み合わせないでください。メッセージをクライアントで読み込む場合は省略します。
API リファレンス#
MessageScroller.Provider#
スタイルなしのルートです。スクロール状態と動作用 props を管理し、パーツとフックへ提供します。独自の DOM は描画しません。
| Prop | Type | Default | Description |
|---|---|---|---|
autoScroll | boolean | false | 読者がすでにライブ エッジにいる間だけ新しい内容に追従します。ホイール、タッチ、キーボード スクロール、明示的な移動で解除されます。 |
defaultScrollPosition | "start" | "end" | "last-anchor" | "end" | 空でない最初の描画時に一度だけ適用する開始位置です。"last-anchor" は最後の scrollAnchor 行から開き、ターンが収まる場合やアンカーがない場合は "end" にフォールバックします。"end" と "last-anchor" では、位置が適用されるまでビューポートに data-pending-scroll が付きます。再読み込み時のちらつきを防ぐを参照してください。 |
scrollEdgeThreshold | number | 8 | 先頭または末尾にいると判定する、各端からの距離です。状態属性とスクロール ボタンの表示を制御します。 |
scrollMargin | number | 0 | scrollToMessage、表示状態、プログラムによる対象に適用する、整列先の端からのマージンです。 |
scrollPreviousItemPeek | number | 64 | 新しく追加された scrollAnchor 項目を配置する際、前の項目の一部を表示し続けるため scrollMargin に加える余白です。 |
MessageScroller.Root#
フレーム兼レイアウト コンテナです。親要素いっぱいに広がるため、MessageScroller.Provider 内の高さを制約したレイアウトで使用します。
| Prop | Type | Default | Description |
|---|---|---|---|
...props | React.ComponentProps<"div"> | - | フレーム要素に展開する props です。 |
ルートは以下のスクロール状態属性をミラーリングします(ビューポートにも付与されます)。そのため、フレーム端のフェードなど、
スクロール状態に応じてコンテナをスタイル設定できます。スタイルなしで使う場合は、開始時のスクロール用レイアウトを維持するため、
data-pending-scroll がある間は visibility: hidden でビューポートを隠してください。
| Data attribute | Value | Description |
|---|---|---|
data-scrollable | "start" | "end" | "start end" | absent | ビューポートがスクロールできる方向です。[data-scrollable~="end"] のように検索できます。存在しない場合は内容が収まっています。 |
data-autoscrolling | present | ビューポートが最新メッセージへプログラムでスクロールしている間に存在します。 |
data-pending-scroll | present | defaultScrollPosition("end" または "last-anchor")が適用されるまで存在します。空のトランスクリプトでは省略されます。設定中はビューポートを隠してください。再読み込み時のちらつきを防ぐを参照してください。 |
MessageScroller.Viewport#
スクロール可能なビューポートです。
| Prop | Type | Default | Description |
|---|---|---|---|
preserveScrollOnPrepend | boolean | true | 古い行を先頭に追加したとき、最初に表示されていたメッセージ項目を同じ位置に保ちます。 |
role | string | "region" | ラベル付きスクロール可能トランスクリプト ビューポートのランドマーク ロールです。 |
aria-label | string | "Messages" | スクロール可能なチャット トランスクリプトのアクセシブルな名前です。 |
tabIndex | number | 0 | トランスクリプト ビューポートをキーボードでスクロール可能にします。 |
...props | React.ComponentProps<"div"> | - | ビューポート要素に展開する props です。 |
| Data attribute | Value | Description |
|---|---|---|
data-scrollable | "start" | "end" | "start end" | absent | ビューポートがスクロールできる方向です。[data-scrollable~="end"] のように検索できます。存在しない場合は内容が収まっています。 |
data-autoscrolling | present | ビューポートが最新メッセージへプログラムでスクロールしている間に存在します。 |
data-pending-scroll | present | defaultScrollPosition("end" または "last-anchor")が適用されるまで存在します。空のトランスクリプトでは省略されます。設定中はビューポートを隠してください。再読み込み時のちらつきを防ぐを参照してください。 |
MessageScroller.Content#
トランスクリプトのコンテンツ要素です。直接の子要素はすべて MessageScroller.Item にします。
| Prop | Type | Default | Description |
|---|---|---|---|
role | string | "log" | ライブ通知用にメッセージ リストへ適用する ARIA ロールです。 |
aria-relevant | string | "additions" | 通知するライブ リージョンの更新です。既定では新しいトランスクリプト行だけです。 |
aria-busy | boolean | - | 必要に応じて、ターンのストリーミング中にライブ リージョンがビジーであることを示します。 |
spacerClassName | string | - | アンカー行のための空間を確保する内部スペーサーのクラス名です。 |
...props | React.ComponentProps<"div"> | - | コンテンツ要素に展開する props です。 |
MessageScroller.Item#
トランスクリプトの 1 行です。メッセージ、マーカー、入力中の行、区切り、追加読み込み行などを表します。
| Prop | Type | Default | Description |
|---|---|---|---|
messageId | string | - | scrollToMessage、表示状態、先頭追加時の位置保持に使う安定した行 ID です。 |
scrollAnchor | boolean | false | 新しく追加されるターンの基準となるターン境界として、この行をマークします。 |
...props | React.ComponentProps<"div"> | - | 項目要素に展開する props です。 |
| Data attribute | Value | Description |
|---|---|---|
data-message-id | string | 指定されている場合、messageId を反映します。 |
data-scroll-anchor | "true" | "false" | scrollAnchor を反映します。 |
MessageScroller.Button#
トランスクリプトの先頭または末尾へスクロールするボタンです。スクロール先がない場合は inert になり、タブ順から除外されます。
| Prop | Type | Default | Description |
|---|---|---|---|
behavior | ScrollBehavior | "smooth" | ボタンが対象の端へスクロールするときに使うネイティブのスクロール動作です。 |
direction | "start" | "end" | "end" | ボタンが向かうトランスクリプトの端です。 |
children | React.ReactNode | - | ボタンのカスタム コンテンツです。既定ではスクロール アイコンとアクセシブル ラベルを表示します。 |
render | React.ReactElement | render function | - | カスタムの描画先です。 |
...props | React.ComponentProps<"button"> | - | ボタンに展開する props です。 |
| Data attribute | Value | Description |
|---|---|---|
data-direction | "start" | "end" | direction を反映します。 |
data-active | "true" | "false" | 現在このボタンでスクロールできるかを示します。 |
useMessageScroller#
命令的にトランスクリプトを操作するための API です。
| Method | Type | Description |
|---|---|---|
scrollToMessage | (messageId: string, options?) => boolean | マウント済みのメッセージ ID へスクロールします。 |
scrollToEnd | (options?) => boolean | 最新のメッセージへスクロールします。 |
scrollToStart | (options?) => boolean | 上部へスクロールします。 |
すべてのコマンドは適用できなかった場合に false を返します。
scrollToStart と scrollToEnd は、ビューポートがまだマウントされていない場合だけ false を返します。
scrollToMessage は、対象がマウントされておらずキューにも追加できない場合に false を返します。
コマンドのオプション:
| Option | Type | Default | Description |
|---|---|---|---|
align | "start" | "center" | "end" | "nearest" | "start" | メッセージ対象をビューポート内でどのように整列するかです。 |
behavior | ScrollBehavior | "auto" | コマンドに使うネイティブのスクロール動作です。 |
scrollMargin | number | provider scrollMargin | このコマンドで整列先の端に適用するマージンです。 |
useMessageScrollerScrollable#
JavaScript で値が必要な周辺 UI 向けに、ビューポートがスクロールできる方向を返します。
スクローラー自体のスタイルには data-scrollable 属性を優先してください。
| Value | Type | Description |
|---|---|---|
start | boolean | ビューポートが先頭方向へスクロールできるかを示します。上にコンテンツが隠れている状態です(!start は先頭を示します)。 |
end | boolean | ビューポートが末尾方向へスクロールできるかを示します。下にコンテンツが隠れている状態です(!end は末尾を示します)。出力追従が読者をライブ エッジに保っている間は false のままです。 |
useMessageScrollerVisibility#
アウトライン、検索、アクティブ ターン UI の表示状態です。useMessageScrollerScrollable とは別に購読するため、
利用側が必要とする場合だけ表示状態の処理コストが発生します。
| Value | Type | Description |
|---|---|---|
currentAnchorId | string | null | 読み取りライン上またはそれより上にある最後の scrollAnchor 項目を基準にした、現在のアンカー ターンです。 |
visibleMessageIds | string[] | ビューポートと交差するメッセージ ID をドキュメント順で返します。 |
ユーザー メッセージ、アンカー ターン、検索結果など、より限定したアウトラインが必要な場合は、アプリ内で visibleMessageIds を絞り込んでください。