Chat - Headless layout
Use Chat.Root and Chat.Layout to define the main chat shell and split the interface into conversation and thread panes.
Chat.Root
Chat.Root wraps ChatProvider and exposes a root slot for the outer container.
It accepts the same runtime props as the headless provider, then renders your chat surface inside a structural root element.
Use Chat.Root when you want:
- provider setup colocated with the UI shell
- controlled or uncontrolled chat models at the app boundary
- slot replacement for the top-level wrapper
Because Chat.Root forwards the headless provider props, it can own:
adapter- controlled or uncontrolled
messages - controlled or uncontrolled
conversations - active conversation selection
- composer value control
- runtime callbacks such as
onToolCall,onFinish,onData, andonError
That keeps runtime setup close to the structural entry point without moving structural guidance into the headless docs.
Chat.Layout
Chat.Layout is the pane manager for the headless layer.
It renders a root plus separate pane slots for conversations and the active thread.
It supports:
- two-pane layouts with both conversation and thread children
- single-pane layouts where only one side is rendered
- pane detection based on primitive markers
- reversed child order without losing pane placement
- pane slot replacement through
slotsandslotProps
The default layout is intentionally small. Its job is to place panes and expose owner state, not to decide visual density, breakpoints, or design tokens.
Pane detection
The layout recognizes marked pane components such as ConversationList.Root and Conversation.Root.
That means you can write:
<Chat.Layout>
<Conversation.Root />
<ConversationList.Root />
</Chat.Layout>
and still get the conversation pane rendered before the thread pane in the final layout structure.
If only one unmarked child is present, Chat.Layout treats it as the thread pane by default.
This makes single-thread layouts straightforward:
<Chat.Layout>
<Conversation.Root>{/* thread-only view */}</Conversation.Root>
</Chat.Layout>
Slot model
The layout exposes:
rootconversationsPanethreadPane
This is useful when you need semantic wrappers such as aside and main, or when a layout system expects custom container elements.
For example:
<Chat.Layout
slots={{
conversationsPane: 'aside',
threadPane: 'main',
}}
slotProps={{
conversationsPane: { 'aria-label': 'Conversations' },
threadPane: { 'aria-label': 'Active thread' },
}}
>
<ConversationList.Root />
<Conversation.Root />
</Chat.Layout>
One-pane and two-pane guidance
Use a two-pane layout when:
- conversation switching happens inside the same page
- the product behaves like an inbox or agent workspace
Use a one-pane layout when:
- the conversation is already chosen by routing
- the page is dedicated to a single thread
- the conversation list lives somewhere else in the application shell
Recommended patterns
- Use
Chat.Layoutfor desktop split-pane surfaces. - Render just
Conversation.RootinsideChat.Layoutfor focused thread pages. - Replace the pane slots when the surrounding page already defines grid or landmark semantics.
For the canonical end-to-end shell, continue with Composition. For the inbox rail itself, continue with Conversation list.