# Thread
URL: /docs/primitives/thread
Build custom scrollable message containers with auto-scroll, empty states, and message rendering.
The Thread primitive is the scrollable message container, and the backbone of any chat interface. It handles viewport management, auto-scrolling, empty states, message rendering, and suggestions. You provide the layout and styling.
```tsx
import {
AuiIf,
ComposerPrimitive,
ThreadPrimitive,
MessagePrimitive,
} from "@assistant-ui/react";
import { ArrowUpIcon } from "lucide-react";
function MinimalThread() {
return (
s.thread.isEmpty}>
`, and `Messages` iterates over the thread's messages. Add your own styles and components; the primitive handles the rest.
Runtime setup: primitives require runtime context. Wrap your UI in `AssistantRuntimeProvider` with a runtime (for example `useLocalRuntime(...)`). See [Pick a Runtime](/docs/runtimes/pick-a-runtime).
Core Concepts \[#core-concepts]
Viewport & Auto-Scroll \[#viewport--auto-scroll]
`Viewport` is the scrollable container. It auto-scrolls to the bottom as new content streams in, but only if the user hasn't scrolled up manually. Set `autoScroll={false}` to disable this entirely.
```tsx
{/* messages */}
```
Turn Anchor \[#turn-anchor]
By default, new messages appear at the bottom and scroll down. With `turnAnchor="top"`, the user's message anchors to the top of the viewport. This creates the modern reading experience where you see the question at the top and the response flowing below it.
```tsx
{/* messages */}
```
This is what the shadcn Thread component uses by default. For scroll anchoring to work correctly, `ViewportSlack` is needed on the last assistant message to provide enough min-height for the user message to anchor at the top. This is included automatically in the shadcn component.
Viewport Scroll Options \[#viewport-scroll-options]
`ThreadPrimitive.Viewport` has three event-specific scroll controls:
* `scrollToBottomOnRunStart` (default `true`): scrolls when `thread.runStart` fires
* `scrollToBottomOnInitialize` (default `true`): scrolls when `thread.initialize` fires
* `scrollToBottomOnThreadSwitch` (default `true`): scrolls when `threadListItem.switchedTo` fires
These work alongside `autoScroll`. If `autoScroll` is omitted, it defaults to `true` for `turnAnchor="bottom"` and `false` for `turnAnchor="top"`.
```tsx
{({ message }) => {
if (message.role === "user") return ;
return ;
}}
```
ViewportFooter \[#viewportfooter]
`ViewportFooter` sticks to the bottom of the viewport and registers its height so the auto-scroll system accounts for it. This is where you place your composer:
```tsx
{() => }
```
Empty State \[#empty-state]
`ThreadPrimitive.Empty` is deprecated. Use [`AuiIf`](/docs/api-reference/primitives/assistant-if) instead.
```tsx
s.thread.isEmpty}>
Welcome!
How can I help you today?
```
Messages Iterator \[#messages-iterator]
`Messages` now prefers a children render function. It gives you the current message state so you can branch inline:
```tsx
{({ message }) => {
if (message.composer.isEditing) return ;
if (message.role === "user") return ;
return ;
}}
```
`components` is deprecated. Use the `children` render function instead.
Suggestions Iterator \[#suggestions-iterator]
`Suggestions` follows the same pattern. Prefer the children render function when rendering custom suggestion UIs:
```tsx
{() => }
```
Parts \[#parts]
Root \[#root]
Top-level container for a thread layout. Renders a `
` element unless `asChild` is set.
```tsx
{() => }
```
Viewport \[#viewport]
The scrollable area with auto-scroll behavior. Renders a `
` element unless `asChild` is set.
```tsx
{({ message }) => {
if (message.role === "user") return ;
return ;
}}
```
ViewportFooter \[#viewportfooter-1]
Footer container that registers its height with the viewport scroll system. Renders a `
` element unless `asChild` is set.
```tsx
...
```
ViewportProvider \[#viewportprovider]
Provides viewport context without rendering a scrollable element. Use this when you have a custom scroll container.
```tsx
{() => }
```
ViewportSlack \[#viewportslack]
Adds min-height for scroll anchoring with `turnAnchor="top"`. It wraps its child element via `Slot` and does not render a DOM element of its own.
```tsx
```
Props: `fillClampThreshold` and `fillClampOffset` control how the slack height is calculated. `children` is required.
Messages \[#messages]
Renders a component for each message in the thread, resolved by role and edit state.
```tsx
{({ message }) => {
if (message.composer.isEditing) return ;
if (message.role === "user") return ;
return ;
}}
```
MessageByIndex \[#messagebyindex]
Renders a single message at a specific index in the thread.
```tsx
```
ScrollToBottom \[#scrolltobottom]
Scrolls the viewport to the bottom. Automatically disabled when already at the bottom. Renders a `