Chat
ChatLayout
| Name | Type | Default | Description |
|---|---|---|---|
density | 'compact' | 'balanced' | 'spacious' | 'balanced' | Spacing, bubble width, and the frosted dock. Messages can override it. |
scrollRef | HTMLElement | null | - | Element that scrolls instead of the layout. Leave it unset and the layout scrolls itself. The dock is sticky for self scroll and fixed for an external scroller. |
showScrollButton | boolean | true | Renders the built-in scroll-to-bottom button. Set false when the scrollButton slot replaces it, or when the button should be absent. |
| Name | Description |
|---|---|
default | The transcript. Usually one ChatMessageList. |
composer | Pinned to the bottom, above the frosted scrim. |
empty | Shown when the default slot is omitted. |
scrollButton | Replaces the built-in button. Props: isVisible, label (set only while new messages are waiting), and scrollToBottom. |
A template ref exposes element and scrollToBottom(). scrollToBottom clears the new-message label and springs to the bottom.
ChatLayoutScrollButton
| Name | Type | Default | Description |
|---|---|---|---|
isVisible | boolean | - | Whether the pill is shown. A hidden pill stays mounted so it can fade, and is marked inert. |
label | string | - | Widens the pill. Omit it for the icon-only down arrow. The accessible name falls back to “Scroll to bottom”. |
Emits click when the button is pressed.
ChatMessageList
| Name | Type | Default | Description |
|---|---|---|---|
align | 'top' | 'bottom' | 'bottom' | Vertical alignment when the list is shorter than its container. 'bottom' keeps a spacer above the rows; 'top' drops it so rows start at the top. |
density | 'compact' | 'balanced' | 'spacious' | 'balanced' | Passed to messages that do not set their own. |
gap | SpacingStep | - | Gap between top-level rows, on the spacing scale. Overrides the density gap without changing padding. |
scrollToTopAction | () => Promise<void> | - | Loads older messages when the top sentinel intersects the scroll parent. A spinner shows while the promise is pending. A second intersection is ignored until it settles. |
isStreaming | boolean | false | Sets aria-busy while an assistant message is still streaming. |
label | string | localized “Messages” | Accessible name of the log. |
The default slot is the rows. The emptyState slot shows when default is omitted; empty is its alias. The root is role="log" with aria-live="polite".
ChatMessage
| Name | Type | Default | Description |
|---|---|---|---|
sender | 'user' | 'assistant' | 'system' | - | Who the row is from. system is centered and ignores avatar, name, and metadata. |
name | string | - | Plain sender name. Prefer the bubble name when the body starts with a bubble. |
density | 'compact' | 'balanced' | 'spacious' | the list, then 'balanced' | Density for this row only. |
Slots: default (body), avatar, name (replaces the prop), metadata. The article is named from the visible name, or from “Message from {sender}”.
ChatMessageBubble
| Name | Type | Default | Description |
|---|---|---|---|
variant | 'filled' | 'ghost' | 'filled' | filled paints the sender surface. ghost drops the background and the block padding so mixed content still lines up. |
name | string | - | Sender name aligned to the bubble padding. |
group | 'first' | 'middle' | 'last' | - | Tightens a corner when several bubbles stack. Leave it unset for a standalone bubble. |
Sender and density come from the surrounding ChatMessage. Slots: default, name, metadata. Attributes fall through to the bubble element.
ChatMessageMetadata
| Name | Type | Default | Description |
|---|---|---|---|
timestamp | string | - | Short time text. The timestamp slot replaces it. |
footer | string | - | Secondary text, such as a model name. The footer slot replaces it. |
status | 'sending' | 'sent' | 'delivered' | 'read' | 'error' | - | Delivery state. The row renders nothing when timestamp, footer, and status are all absent. |
The status icon’s accessible name is localized, for example “Message read”. On a user row the items run from the inline end.
ChatSystemMessage
| Name | Type | Default | Description |
|---|---|---|---|
variant | 'default' | 'divider' | 'default' | default is a centered line. divider draws a rule on each side. The root is role="status". |
Slots: default, and icon (ignored for divider).
ChatToolCalls
| Name | Type | Default | Description |
|---|---|---|---|
calls | ChatToolCallItem[] | - | Tool calls from one assistant turn. An empty array renders nothing. Each item may set name, status, target, duration, node, additions, deletions, stats, errorMessage, key, resultDetail, and data. detail is an alias of resultDetail. |
label | string | localized “{count} tool calls” | Summary shown while a group is expanded. |
v-model:isExpanded | boolean | - | Controlled expanded state of a multi-call group. |
defaultIsExpanded | boolean | false | Initial state while isExpanded is unbound. |
Emits expandedChange with the next boolean. The detail slot receives { call } and replaces call.resultDetail. A collapsed group shows the latest call; the panel is inert until opened. Pending and running calls use a spinner. An error is spoken from a visually hidden string.
ChatReasoning
| Name | Type | Default | Description |
|---|---|---|---|
label | string | localized “Thinking” | Header label. |
duration | string | - | Shown after the label once streaming has finished, such as 12s. |
isStreaming | boolean | false | Shimmers the label and hides the preview and the duration. |
v-model:isExpanded | boolean | - | Controlled expanded state. |
defaultIsExpanded | boolean | false | Initial state while isExpanded is unbound. |
The default slot is the full trace. A single text node is also the collapsed preview. Emits expandedChange. The panel is inert while collapsed.
ChatTypingIndicator
| Name | Type | Default | Description |
|---|---|---|---|
names | string[] | - | People currently typing. Empty names are dropped. No names leaves the dots only. One, two, and three or more each get their own sentence. |
The root is role="status" with aria-live="polite". The sentence lives in that region and is not announced again through useAnnounce.
ChatUnreadDivider
| Name | Type | Default | Description |
|---|---|---|---|
label | string | localized “New” | Short label at the end of the rule. The accessible name is “{label} messages below”. |
The root is role="separator". It is not a .astryx-chat-message, so the new-message tracker does not treat it as a message.
ChatComposer
| Name | Type | Default | Description |
|---|---|---|---|
modelValue | string | - | Controlled draft, bound with v-model. Omit it and the shell keeps the draft. |
placeholder | string | The localized “Type a message…” | Shown while the draft is empty. |
isDisabled | boolean | false | Disables editing and send. Stop stays clickable when it is shown. |
density | 'compact' | 'balanced' | 'spacious' | 'balanced' | Padding inside the body. Spacious uses the same spacing as balanced. |
elevation | 'low' | 'none' | 'low' | Low deepens on hover and focus. None is flat and uses a border. The focus ring appears only when the keyboard focuses the editor. |
isStopShown | boolean | false | Shows stop instead of send. |
status | { type: 'error' | 'warning', message?: string } | - | Error uses alert. Warning uses status. |
statusPosition | 'top' | 'bottom' | 'bottom' | Places the bar above or below the body, tucked behind the radius. |
| Name | Payload | Description |
|---|---|---|
submit | value: string | Fires for a trimmed, non-empty draft, then clears it. |
stop | - | Stop was activated. |
change | value: string | The draft changed, including the clear after submit. |
update:modelValue | value: string | Emitted with change for v-model. |
Slots are drawer, headerActions, headerContext, input, footerActions, sendActions, and sendButton. The template ref exposes element.
ChatComposerInput
| Name | Type | Default | Description |
|---|---|---|---|
modelValue | string | - | Controlled draft. Inside a shell, an omitted value follows the shell. |
placeholder | string | The shell or the localized placeholder | Hint on the empty editor. |
maxRows | number | 8 | The editor scrolls after this many lines. A line is 22px. |
triggers | ChatComposerTrigger[] | - | A trigger character at a word boundary opens suggestions. hasMultiWordQuery on a trigger keeps the query through spaces and punctuation until a line break, 64 characters, or the same trigger character again. Without it, a space or tab ends the query. An unmatched multi-word query hides the list when emptySearchText is omitted. The editor becomes a combobox. |
debounceMs | number | 150 | Delay for an async search. Sync results are immediate, and the first async search starts at once. |
hasHistory | boolean | true | Arrow Up at the start recalls the previous submission. Arrow Down at the end steps forward. |
label | string | The localized “Message input” | Accessible name of the editor. |
isDisabled | boolean | The shell's disabled state | Disables editing. |
pasteAsToken | Return value of useChatPasteAsToken | false | Pastes longer than 200 characters become a chip | Pass false to insert the text as-is. |
pasteAction | (event, text) => boolean | void | - | First chance at a plain-text paste. Return true and the input will not insert it. |
Events are change, update:modelValue, submit, files, and keydown. files comes from a paste or a drop, and a drop does not navigate away. keydown fires after an open suggestion list declines the event. preventDefault skips Enter-to-send and history. The template ref exposes insertToken, expandToken, insertText, focus, and getValue.
ChatComposerDrawer
| Name | Type | Default | Description |
|---|---|---|---|
count | number | - | Enables collapse and appears in the default summary. |
label | string | The localized “Items” | Used in the summary and in the expand and collapse names. |
isCollapsed | boolean | - | Controlled collapsed state, bound with v-model:is-collapsed. |
defaultIsCollapsed | boolean | false | Initial state while unbound. |
collapsedChange fires when the collapsed state changes. The default slot is the open content. collapsedSummary replaces the count and label. The button still owns the disclosure name.
ChatSendButton
| Name | Type | Default | Description |
|---|---|---|---|
isStopShown | boolean | The shell's stop state | Stop is secondary. Send is primary. Stop ignores disabled. |
isDisabled | boolean | The draft is empty or the shell is disabled | Disables send only. |
size | 'sm' | 'md' | 'md' | Button size. The shell rounds it to the outer radius. |
Events are send, stop, and click. sendIcon and stopIcon replace the arrow and the square. Inside a shell, a click with no listener of its own submits the draft or stops the shell.
ChatDictationButton
Pass the return value of useChatDictation or useSpeechRecognition. size defaults to md. isHiddenWhenUnsupported defaults to true, so the control is absent when speech recognition is missing. Set it to false and the button stays, disabled. label replaces the start or stop dictation name. While listening, five bars follow the volume, and a loud signal blends the accent color toward the error color.
ChatTokenizedText
The default slot holds the message text, and the text prop does the same job when a string is easier to pass. Each non-empty value in tokens is matched literally and replaced with a badge or custom content. Empty values are ignored. The same objects a trigger returns can be reused here.
ChatComposerTokenElement
Wraps one chip so it cannot be edited inside the field and carries the serialized value. The input inserts these itself. Use it directly when a chip should render outside the editor.
Composables
useChatStreamScroll({ scrollRef }) follows the bottom of scrollRef. The first time the content can scroll it jumps; later growth uses a spring, unless reduced motion is requested. Scrolling up unlocks at once. Settling within 10px of the bottom locks again. isScrolledUp feeds the button.
useChatNewMessages({ isLocked, onResize }) watches the transcript height. When the last .astryx-chat-message changes while unlocked, hasNewMessages becomes true until dismiss(). ChatLayout announces that change.
useChatStreamScroll, useChatNewMessages, and useAnnounce are exported from @astryx-vue/core. Reduced motion is read with useMediaQuery.
The composer also exports useChatComposerContext, useChatComposerTokens, useChatPasteAsToken, useChatDictation, and useSpeechRecognition. Import them from the Chat entry. The package root re-exports them. Speech recognition uses the browser API and does not bind a speech service. A paste longer than 200 characters becomes a chip by default.
Theming
packages/themes/neutral/chat.css maps component variables onto semantic tokens. Another theme overrides those variables.
--astryx-chat-bubble-background/--astryx-chat-foreground: filled bubble surface and text, mapped from--astryx-color-neutraland--astryx-color-text-primary.--astryx-chat-foreground-secondary,--astryx-chat-foreground-muted: metadata, system lines, and secondary tool text.--astryx-chat-error,--astryx-chat-success,--astryx-chat-accent: failed status, deletions, and a running tool.--astryx-chat-unread-color: the unread rule and its label. Neutral maps it to--astryx-color-border-red, so both viewports use one color.--astryx-chat-focus: keyboard outline on disclosure buttons.--astryx-chat-scroll-background/--astryx-chat-scroll-shadow: the scroll-to-bottom pill.--astryx-chat-radius: standalone bubble radius. Compact density uses--astryx-chat-radius-compact. Grouped bubbles use--astryx-chat-radius-groupon the inner corners.--astryx-duration-fast-max,--astryx-duration-medium-max,--astryx-duration-slow-max,--astryx-ease-standard: pill, disclosure, and typing motion.
Native attributes
ChatLayout, ChatMessageList, ChatMessage, ChatSystemMessage, ChatToolCalls, ChatReasoning, ChatTypingIndicator, ChatUnreadDivider, ChatComposer, ChatComposerDrawer, ChatComposerInput, and ChatTokenizedText forward attributes to their root. ChatMessageBubble forwards them to the bubble, not the name or metadata. ChatMessageMetadata forwards them to the metadata row. ChatSendButton and ChatDictationButton forward them to the button.