API and Development
Chat assembles Conversation, Message, and PromptInput into a ready-made surface with one owner of spacing. Pass variant={ChatVariant.Default} (or omit variant) for bubbled user turns and a reflowing pill composer that floats over the transcript with a surface-colour scrim, or ChatVariant.Annotation for flat turns with a docked stacked (flush, divider-separated) composer in the annotation popover. Every chrome difference lives in CHAT_VARIANT_CHROME — including promptLayout (Reflow / Stacked), composerPlacement (Floating / Docked), and metaPlacement (Inline / Below) — do not scatter form-factor conditionals in consumers. Composer region padding is always zero; PromptInput owns its own layout insets.
Chat fills its parent and keeps the transcript as the only scroll region — the composer stays pinned in both form factors. Give the host a bounded height (a fixed height, a max-height, or a flex chain with min-height: 0); in an unbounded parent the transcript grows to its content height and never scrolls.
Empty Default chats with neither messages nor emptyState keep the composer in document flow (data-composer="docked") so content-sized hosts do not collapse around an absolutely positioned pill. As soon as a transcript or emptyState is present, Default floats the composer again. Annotation always docks the stacked composer, so empty Annotation chats stay in flow without that special case.
| Name | Type | Default | Description |
|---|---|---|---|
messages | ChatMessage[] | — | Transcript in order; omit or pass [] for the empty state. |
prompt | ChatPrompt | required | Composer config (onSubmit, placeholder, attach, chips, suggestions, …). layout is owned by the variant. |
variant | ChatVariant | Default | Form factor: default or annotation. |
messageVariant | MessageContentVariant | — | Overrides the variant's per-role bubble treatment, applying one treatment to every turn. Use it with showAuthor={false}, where the bubble is the only turn marker and the per-role split reads as an inconsistency. |
status | PromptInputStatus | "ready" | Submission status. While "submitted" or "streaming", send is blocked; with prompt.onStop, the trailing control becomes a stop button. |
emptyState | EmptyStateProps | — | Props passed through to EmptyState when there are no messages. |
scrollToLatestLabel | string | — | Accessible label for the scroll-to-latest control. |
className | string | — | Optional class merged onto the chat root. |
| Name | Type | Description |
|---|---|---|
attach | PromptInputAttachActions | Shows the leading add control (images / files / …). |
chips | PromptInputChip[] | Persistent context chips above the typed area. See Context chips vs inline chips. |
onChipDismiss | (id: string) => void | Fires when a context chip or an inlined chip is dismissed. |
addLabel | string | Accessible label for the add control. |
onStop | () => void | Stops an in-flight generation. When set and status is "submitted" or "streaming", the trailing control becomes Secondary StopCircle. |
stopLabel | string | Accessible label for the stop control. Defaults to a localized "Stop generating". |
PromptInput has two chip surfaces, and they are not interchangeable.
chips (context row) | insertChip (inline) | |
|---|---|---|
| Ownership | Declarative — pass the full set every render | Imperative, via PromptInputHandle |
| Placement | Own row above the typed area | Inside the text flow, at the caret |
In onSubmit | Never | Yes, as @label in message and a chip segment |
| Survives submit | Yes | No, the composer clears |
Survives a controlled value change | Yes | No, the editable is replaced |
Use chips for pinned context the message applies to (tagged elements, files, records): the row mirrors the array exactly, so the owner's store stays the single source of truth and removal flows through onChipDismiss. Use insertChip only for chips that belong in the sentence, such as an accepted suggestion.
Do not drive chips from an imperative handle and a controlled value at once. Clearing on submit and replacing the editable on an external value change both destroy inline chips, which is what the context row exists to avoid.
Declared on .ai-chat when composerPlacement is Floating (Default variant):
| Name | Default | Description |
|---|---|---|
--ai-chat-composer-scrim | var(--color-background-base) | Opaque surface colour the gradient fades into. Chat samples the nearest opaque parent background at runtime and writes an inline value on the chat root; that beats author rules targeting .ai-chat. To force a colour, set the variable on a child of the root (or clear the inline property). The CSS default is the fallback when no opaque parent is found. |
--ai-chat-composer-height | measured | Published by Chat via ResizeObserver; used for transcript bottom padding and lifting the scroll-to-latest control. |
| Name | Type | Description |
|---|---|---|
id | string | Stable list key. |
from | MessageRole | Author role (user / assistant / …). |
author | MessageAuthor | Display name and optional avatar (src image URL wins over icon glyph). Demo Ally photo: ALLY_GORITHM_AVATAR_SRC. |
meta | ReactNode | Turn metadata (timestamp, "Worked for 5m 26s"). CHAT_VARIANT_CHROME decides whether it trails the author name (MessageMetaPlacement.Inline, used by Default) or stacks beneath it (MessageMetaPlacement.Below, used by Annotation). With showAuthor={false} there is no author row to place it in, so Message renders it as a caption beneath the body instead and metaPlacement does not apply. |
text | string | Markdown sugar via Response when content is absent. |
streaming | boolean | Marks text as a live token stream. |
content | ReactNode | Rich turn body; wins over text. |
CodeBlock from @prepared911/ui-ai-elements renders a fenced code surface with a language label, optional copy control, and Shiki highlighting using github-light / github-dark-dimmed (same themes as the design docs site). Dark is the default token color; light theme switches via .light / .light-theme ancestors from ThemeProvider.
| Name | Type | Default | Description |
|---|---|---|---|
code | string | required | Raw source to display and copy. |
language | string | — | Header label and Shiki language id (aliases like ts → typescript are normalized). |
showCopy | boolean | true | When false, hides the copy IconButton. |
className | string | — | Optional class merged onto the root. |
Response maps markdown fenced code nodes to CodeBlock.Tool stringifies input/output and renders them with language="json".Chat owns transcript + composer spacing for both form factors.Shimmer is an animated in-progress label (e.g. "Thinking…" / "Reasoning"). A highlight sweeps across the glyphs via a clipped gradient; when an optional leading icon is provided, its fill sweeps in phase with the text. When active is false it renders as plain muted text. Respects prefers-reduced-motion.
Active and icon paths root in FlexBox (a div). Prefer placing Shimmer in flex/block parents, not inside a paragraph.
| Name | Type | Default | Description |
|---|---|---|---|
type | TextType | BodySmall | Typography variant for the text. |
active | boolean | true | When true, animates a highlight sweeping across the text and icon. |
asSpan | boolean | true | When true, the active/icon root uses display: inline-flex and the label text renders as a span. The root itself is still a div — do not nest inside a <p>. When inactive with no icon, the control collapses to a CoreText span. |
icon | SVGAsset | — | Optional leading icon; fill sweeps in phase with the text. |
className | string | — | Optional class merged onto the root. |
On this page