JGuang

Chat

Chat · Astryx

Preview
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

Chat

Chat is a family of composable primitives for building AI and human chat experiences. Combine ChatLayout, ChatMessageList, ChatMessage, bubbles, system messages, tool calls, tokenized text, and ChatComposer to assemble complete conversations without reimplementing sender-aware layout, density, scrolling, or composer behavior.

Installation

npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

Requires React 19+, base CSS and a Theme provider. See Setup.

Chat

Chat is a family of composable primitives for building AI and human chat experiences. Combine ChatLayout, ChatMessageList, ChatMessage, bubbles, system messages, tool calls, tokenized text, and ChatComposer to assemble complete conversations without reimplementing sender-aware layout, density, scrolling, or composer behavior.

Usage guidance

  • Compose messages using MessageList > Message > Bubble for consistent sender-aware styling and density.
  • Set the density prop to control spacing globally: compact for sidebars, balanced for most views, spacious for long-form reading. Individual messages can override.
  • Use gap when top-level rows are independent (for example, LLM tool events or streamed blocks) and list spacing needs to be tuned separately from density.
  • Use the group prop on bubbles (first, middle, last) when a single sender sends multiple consecutive messages; it tightens corner radius to visually connect them.
  • Use ChatSystemMessage with variant="divider" for date separators and default for inline status notices like joins, leaves, or topic changes.
  • Put name on the first bubble and metadata on the last bubble in a message so they align with the bubble's inline padding.
  • Wrap custom in-message content (cards, attachments, citations) in a ghost bubble so it aligns with the bubble text column; add width="100%" when it should span the full message column instead of the default bubble width cap.
  • Provide an emptyState prop so new users see a clear prompt to start a conversation instead of a blank screen.
  • Use the ghost bubble variant for AI-style responses that show rich content like code blocks or markdown without a visible boundary.
  • Avoid: Don't use ChatSystemMessage for sender content; it has no avatar, alignment, or bubble. Use ChatMessage with a sender role instead.
  • Avoid: Don't put long or multi-line content in a system message; keep it to a single short sentence. If you need more, use a bubble or a card.
  • Avoid: Don't nest ChatMessage inside another ChatMessage; each message is a standalone article element with its own sender context.
  • Avoid: Don't apply a fixed height directly on the message list; wrap it in a sized container and let the list fill with flex: 1.
  • Avoid: Don't mix filled and ghost bubble variants within the same sender's messages; pick one style per side and use it consistently.
  • Avoid: Don't place metadata or names on both the bubble and the message wrapper; pick one based on whether the content has a bubble boundary.

Theming API

{
  "targets": [
    {
      "className": "astryx-chat-layout",
      "visualProps": [
        "density"
      ]
    },
    {
      "className": "astryx-chat-composer",
      "visualProps": [
        "density"
      ]
    },
    {
      "className": "astryx-chat-composer-input"
    },
    {
      "className": "astryx-chat-composer-drawer",
      "visualProps": [
        "collapsed"
      ]
    },
    {
      "className": "astryx-chat-message",
      "visualProps": [
        "sender",
        "density"
      ]
    },
    {
      "className": "astryx-chat-message-bubble",
      "visualProps": [
        "sender",
        "variant",
        "density"
      ]
    },
    {
      "className": "astryx-chat-message-list",
      "visualProps": [
        "density"
      ]
    },
    {
      "className": "astryx-chat-system-message",
      "visualProps": [
        "variant"
      ]
    },
    {
      "className": "astryx-chat-message-metadata"
    },
    {
      "className": "astryx-chat-send-button"
    },
    {
      "className": "astryx-chat-dictation-button"
    },
    {
      "className": "astryx-chat-layout-scroll-button"
    },
    {
      "className": "astryx-chat-tokenized-text"
    },
    {
      "className": "astryx-chat-tool-calls"
    },
    {
      "className": "astryx-trigger-menu"
    }
  ],
  "vars": [
    {
      "name": "--_chat-composer-radius",
      "description": "Border radius of the composer body. Inner elements derive their radius concentrically.",
      "default": "var(--radius-chat)",
      "private": true
    },
    {
      "name": "--_chat-composer-padding",
      "description": "Padding of the composer body. Used in the concentric radius calculation.",
      "default": "var(--spacing-3)",
      "private": true
    },
    {
      "name": "--_button-radius",
      "description": "Concentric button radius inside the composer.",
      "default": "max(var(--radius-element), calc(var(--_chat-composer-radius) - var(--_chat-composer-padding)))",
      "private": true,
      "derived": true,
      "formula": "max(var(--radius-element), calc(var(--_chat-composer-radius) - var(--_chat-composer-padding)))"
    }
  ],
  "derived": [
    {
      "property": "borderRadius",
      "vars": [
        "--_chat-composer-radius"
      ]
    },
    {
      "property": "padding",
      "vars": [
        "--_chat-composer-padding"
      ]
    }
  ]
}

ChatMessageList

ChatMessage

ChatMessageBubble

ChatMessageMetadata

ChatSystemMessage

ChatComposer

ChatComposerInput

ChatComposerDrawer

ChatSendButton

ChatToolCalls

ChatTokenizedText

ChatComposerTokenElement

ChatLayout

ChatLayoutScrollButton

Anatomy

  • Message area: Scrollable region for messages. Renders children (typically ChatMessageList) in a flex column that pushes content to the bottom when the list is short.
  • Frosted glass dock: Sticky or fixed container at the bottom with a backdrop-blur layer. Houses the scroll button and composer.
  • Scroll-to-bottom button: Appears when the user scrolls up or new messages arrive. Defaults to ChatLayoutScrollButton; pass null to hide or a custom element to override.
  • Composer: The input area for sending messages, typically ChatComposer. Docked at the bottom inside the frosted glass layer.
  • Empty state: Centered placeholder shown when no messages exist. Use EmptyState for a consistent look.
  • Avatar: A sender avatar rendered beside the message. Typically Avatar with size="md". Hidden for system messages.
  • Name: Sender name above the message body. Place on the bubble when using bubbles, or on the message wrapper for raw content.
  • Content: The message body: one or more ChatMessageBubble elements, or any free-form ReactNode like images or tool calls. Wrap non-bubble content in a ghost bubble to align it with the bubble text column.
  • Metadata: Timestamp, delivery status, and footer actions below the message. Place on the last bubble or on the message wrapper.

Chat Composer

ChatComposer is the message-entry shell for a chat surface. It coordinates a controlled or uncontrolled draft, provides a default token-capable input and send/stop action, and arranges optional drawer, header, footer, and status content. Custom inputs and send controls can join the same value, submission, disabled, stop, and focus contract through useChatComposerContext().

PropTypeDefaultDescription
onSubmit *(value: string) => voidCalled when the user submits a message.
onStop() => voidCalled when the user requests to stop generation.
isStopShownbooleanfalseWhether the stop button is shown instead of the send button.
valuestringControlled input value.
onChange(value: string) => voidChange handler for controlled mode.
placeholderstring'Type a message...'Placeholder text shown when the input is empty.
isDisabledbooleanfalseDisables the composer.
density'compact' | 'balanced' | 'spacious''balanced'Visual density.
elevation'none' | 'low''low'Resting elevation of the composer body. low (the default) keeps today's raised look: low at rest, bumping to med on hover / focus. none flattens it and draws a border. Keyboard focus in the editor adds the shared focus ring in either presentation; pointer focus does not add that ring.
drawerReactNodeSlot: collapsible drawer above the input: attachments, context chips, etc. Use ChatComposerDrawer.
headerActionsReactNodeSlot: left-aligned header actions (attach, mention buttons). Use icon-only size="sm" buttons.
headerContextReactNodeSlot: right-aligned contextual info in the header (context window usage, ProgressBar, supporting text).
inputReactNodeSlot: custom input element. Replaces the default input. Use ChatComposerInput for trigger menus/tokens, or wire any input (plain textarea, rich editor) to the composition contract via useChatComposerContext(): read value/onChange/onSubmit/canSend and register a focus control on inputControlRef so body-click-to-focus works.
footerActionsReactNodeSlot: left-aligned footer actions (model selector, etc).
sendActionsReactNodeSlot: actions to the left of the send button.
sendButtonReactNodeSlot: custom send button. Replaces the default send/stop button.
status{ type: 'error' | 'warning'; message?: string }Status message rendered below (or above) the composer.
statusPosition'top' | 'bottom''bottom'Where to render the status.

Anatomy

  • Composer frame: Outer composition root that groups the body with an optional status message above or below it.
  • Composer body: Rounded, elevated surface containing the header, input, and footer.
  • Drawer: Content before the body for attachments or other expandable context, typically ChatComposerDrawer.
  • Header: Row for start-aligned actions and end-aligned contextual information.
  • Input: Default ChatComposerInput or a custom editor connected through useChatComposerContext().
  • Footer: Row for caller-supplied footer and send actions plus the primary action.
  • Send or stop action: Default ChatSendButton or a custom sendButton; it submits the current draft or requests interruption.
  • Status message: Error or warning feedback rendered before or after the composer body.

Chat Composer Drawer

Use ChatComposerDrawer in the ChatComposer drawer slot for supplementary content such as attachments, context chips, or previews. Provide count only when people should be able to collapse that content.

PropTypeDefaultDescription
children *ReactNodeContent to render inside the drawer: tokens, chips, previews, or any React elements.
countnumberTotal item count shown in the collapsed badge. When provided, the drawer gains a collapse/expand toggle.
labelstring'Items'Label shown next to the count in the default collapsed summary and used to name the expand/collapse action.
collapsedSummaryReactNodeVisual content for the complete Collapsed summary anatomy part. When count enables collapse, this replaces the default neutral Badge and label while the component retains disclosure behavior and accessible naming.
isCollapsedbooleanControlled collapsed state. Use with onCollapsedChange for external control.
defaultIsCollapsedbooleanfalseInitial collapsed state for uncontrolled usage.
onCollapsedChange(isCollapsed: boolean) => voidCallback fired when the user toggles the drawer.

Usage guidance

  • Give label a concrete plural noun such as "Attachments" so the expand and collapse actions have a useful accessible name.
  • Use controlled isCollapsed with onCollapsedChange when another part of the page owns drawer state; otherwise use defaultIsCollapsed.
  • Avoid: Put primary composer actions in the drawer; use ChatComposer footer or send-action slots so those controls remain available when the drawer is collapsed.

Theming API

{
  "targets": [
    {
      "className": "astryx-chat-composer-drawer",
      "visualProps": [
        "collapsed"
      ]
    }
  ]
}

Anatomy

  • Root surface: The drawer surface that composes above the ChatComposer body.
  • Disclosure toggle: The keyboard- and pointer-operable collapse control rendered when count is provided.
  • Collapsed summary: The default count Badge and label, or caller-provided visual content, presented while the drawer is collapsed.
  • Content area: The caller-provided supplementary content; collapsed descendants are unavailable to keyboard and assistive technology.

Chat Composer Input

Pass ChatComposerInput to ChatComposer's input slot when the draft needs trigger menus, inline tokens, history recall, dictation insertion, or file intake.

PropTypeDefaultDescription
handleRefReact.Ref<ChatComposerInputHandle>Imperative handle for programmatic control: insertToken, expandToken, insertText, focus, and getValue.
valuestringControlled input value. Pair with onChange for two-way binding.
onChange(value: string) => voidCalled when the input value changes. The serialized string includes token placeholders.
placeholderstring'Type a message...'Placeholder text shown when the input is empty.
maxRowsnumber8Maximum visible rows before the input scrolls. Use a lower value in compact layouts.
triggersChatComposerTrigger[]Trigger definitions for typeahead menus. Each trigger specifies a character (@ or /), a search source, and an onSelect handler that returns the token to insert. Set hasMultiWordQuery: true when suggestions contain spaces (people, topics): the query then runs to a line break or about 64 characters instead of ending at the first space. Per-trigger emptySearchText (ReactNode) is the message when the query matched nothing.
debounceMsnumber150Debounce delay for async search sources to avoid excessive network requests.
hasHistorybooleantrueEnable ArrowUp/Down to recall previously submitted messages.
labelstring'Message input'Accessible label announced by screen readers.
isDisabledbooleanfalseDisables the input. Use during streaming or when a prerequisite is unmet.
onPaste(event, text) => boolean | voidCalled when plain text is pasted. Return true after handling the text yourself; otherwise ChatComposerInput inserts it or applies paste-as-token behavior.
pasteAsTokenUseChatPasteAsTokenReturn | falsePaste-as-token behavior. By default, plain-text pastes over 200 characters become expandable tokens. Pass false to keep every text paste inline, or provide useChatPasteAsToken() to customize the conversion.
onFiles(files: File[]) => voidCalled when files are pasted or dropped onto the input. Use to handle attachments.
onSubmit(value: string) => voidCalled when the user presses Enter without Shift. The serialized value includes token placeholders.
onKeyDown(event) => voidKey-down handler invoked before the built-in Enter/history behavior (after any open trigger menu). The seam for platform-specific keys: call event.preventDefault() to suppress the default submit (e.g. newline on a touch keyboard), or act on the event yourself to add behavior (e.g. submit on Cmd/Ctrl+Enter). IME composition is always respected; Enter never submits mid-composition.

Usage guidance

  • Use handleRef for programmatic text or token insertion; inserted text follows the same onChange pipeline as typing.
  • Use onFiles for files pasted or dropped onto the editor, and keep file validation and upload progress in the product layer.
  • Avoid: Use custom token rendering for ordinary mentions or commands; prefer the structured badge form so tokens remain predictable and themeable.

Anatomy

  • Input root: The themed container that forwards the root ref and BaseProps styling seams.
  • Editable surface: The labeled contenteditable textbox or combobox that owns text, selection, keyboard, paste, and drop behavior.
  • Placeholder: Visual guidance shown only while the serialized draft is empty.
  • Trigger menu: The suggestion popup rendered while a configured trigger is active.
  • Inline token: A non-editable badge or custom rendering with a stable serialized value.

Chat Composer Token Element

Renders a single token chip outside the contentEditable input. Wraps a badge config or custom render function in the correct data-astryx-token span so the token serializes properly and stays visually consistent with tokens inside the composer.

PropTypeDefaultDescription
token *ChatComposerTokenThe token to render. Pass a badge config ({ value, label, variant?, icon? }) for the common case, or a custom render ({ value, render }) for full control.

Chat Dictation Button

ChatDictationButton is a toggle button that starts and stops voice dictation inside a chat composer. It pairs with useChatDictation to show a microphone icon when idle and animated frequency bars when listening. Place it in the sendActions slot of ChatComposer.

PropTypeDefaultDescription
dictation *UseSpeechRecognitionReturnThe return value from useChatDictation or useSpeechRecognition. Controls all button state: listening, volume, bands, and toggle.
size'sm' | 'md''md'Button size. Matches ChatComposer density.
isHiddenWhenUnsupportedbooleantrueWhen true, renders nothing if the browser does not support SpeechRecognition. When false, keeps the button visible but disabled.
labelstringAccessible label override. Defaults to "Start dictation" or "Stop dictation" based on state.
xstyleStyleXStylesStyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value, not an inline style object like style={{}}.

Usage guidance

  • Place the dictation button in the sendActions slot of ChatComposer so it sits next to the send button where users expect voice input controls.
  • Pass an inputRef to useChatDictation so interim transcripts appear as ghost text in the composer input while the user speaks.
  • Enable hasSounds on useChatDictation to give users audio feedback when dictation starts and stops. This is especially helpful when the button's visual change is subtle.
  • Avoid: Don't use the dictation button outside a chat composer context. It's designed for the composer's send-action layout, not as a standalone recording control.
  • Avoid: Don't forget to handle the unsupported case. The button hides itself by default when the browser lacks SpeechRecognition, but you should still design the composer to work without it.

Anatomy

  • Microphone icon: Shown in the idle state. Indicates that tapping will start voice input.
  • Frequency bars: Animated equalizer bars that replace the icon during listening. React to real microphone volume.
  • Ghost button: The underlying Button with ghost variant and isIconOnly, providing the hit target and focus ring.

Chat Layout

ChatLayout is the layout shell for full-page chat interfaces. It renders messages in normal page flow and docks the composer to the bottom with a frosted glass blur layer. Set the density prop to control spacing; it defaults to balanced. Use it to wrap ChatMessageList and ChatComposer for a complete chat experience with built-in auto-scroll and a scroll-to-bottom button.

PropTypeDefaultDescription
children *ReactNodeMessage content: typically ChatMessageList. Flows naturally in the page and scrolls with the container.
composer *ReactNodeComposer element: typically ChatComposer. Fixed to the bottom with a frosted glass dock.
emptyStateReactNodeContent shown when children is empty. Centered vertically in the message area.
scrollButtonReactNode | nullScroll-to-bottom button rendered above the composer. Defaults to ChatLayoutScrollButton with auto-scroll integration. Pass null to hide.
scrollRefReact.RefObject<HTMLElement | null>External scroll container ref. When provided, auto-scroll and scroll-to-bottom target this element instead of the layout root. Use when the chat is embedded in a page where a parent element or the document body scrolls.
density'compact' | 'balanced' | 'spacious''balanced'Visual density: controls dock padding, message-area max-width and inline padding, and the height of the frosted glass blur layer.

Usage guidance

  • Pass ChatMessageList as children and ChatComposer as the composer prop for a complete chat interface.
  • Provide an emptyState so new conversations show a prompt instead of a blank screen.
  • Use scrollRef when the chat is embedded in a page where a parent element handles scrolling.
  • Avoid: Don't apply a fixed height on the layout; let it fill its container with flex: 1.
  • Avoid: Don't render multiple ChatLayout instances in the same scroll container; each expects to own its scroll context.

Anatomy

  • Message area: Scrollable region for messages. Renders children (typically ChatMessageList) in a flex column that pushes content to the bottom when the list is short.
  • Frosted glass dock: Sticky or fixed container at the bottom with a backdrop-blur layer. Houses the scroll button and composer.
  • Scroll-to-bottom button: Appears when the user scrolls up or new messages arrive. Defaults to ChatLayoutScrollButton; pass null to hide or a custom element to override.
  • Composer: The input area for sending messages, typically ChatComposer. Docked at the bottom inside the frosted glass layer.
  • Empty state: Centered placeholder shown when no messages exist. Use EmptyState for a consistent look.

Chat Layout Scroll Button

Floating scroll-to-bottom button that appears when the user scrolls away from the latest messages. It fades in as a compact icon button and expands to show a label when new messages arrive. ChatLayout renders this by default: pass a custom element to the scrollButton prop to override, or null to hide it entirely.

PropTypeDefaultDescription
isVisible *booleanWhether the button is visible. Bind to a scroll-position check so the button only appears when the user has scrolled up.
labelstringOptional label that expands the button (e.g. "New messages"). Use to signal unread content below the fold.
onClick *() => voidClick handler: typically scrolls to bottom and dismisses the new message indicator.

Chat Message

Sender context wrapper: handles avatar, name, metadata, and alignment based on sender role.

PropTypeDefaultDescription
sender *'user' | 'assistant' | 'system'Who sent this message: controls alignment and layout.
children *ReactNodeFree-form content: bubbles, asset lists, tool calls, images. Custom (non-bubble) children render flush with the message edge; wrap them in a ghost ChatMessageBubble to align them with the bubble text column.
avatarReactNodeAvatar element rendered beside the message. Typically Avatar. null, booleans, and empty strings create no slot; any other content that renders nothing collapses its slot.
nameReactNodeSender name rendered above the message body; it labels the message. null, booleans, and empty strings create no slot, and any other content that renders nothing collapses its slot; either way the message is labelled by the translated sender fallback. Use when the first child is raw content (not a bubble). If the first child is a bubble, put the name on the bubble's name prop instead.
metadataReactNodeMetadata rendered below the message body. null, booleans, and empty strings create no slot; any other content that renders nothing collapses its slot. Use when the last child is raw content (not a bubble). If the last child is a bubble, put metadata on the bubble's metadata prop instead.
density'compact' | 'balanced' | 'spacious''balanced'Visual density. Inherited from list context if not set.

Chat Message Bubble

Styled content container for the chat "bubble." Reads sender from parent ChatMessage context to auto-style the background. Use filled for standard messages and ghost when content needs alignment without a visible boundary. Supports name/metadata slots aligned with bubble padding, and multi-bubble grouping via the group prop for consecutive messages from the same sender.

PropTypeDefaultDescription
children *ReactNodeBubble content: text, Markdown, or any ReactNode.
variant'filled' | 'ghost''filled'Visual variant. 'filled' renders sender-colored background (default). 'ghost' renders transparent background but keeps padding for alignment.
nameReactNodeSender name rendered above the bubble, aligned with bubble text padding. Use on the first bubble in a message. Non-rendering scalar values (null, undefined, booleans, and the empty string) omit the aligned wrapper; numeric 0 remains aligned. If the first content is raw (no bubble), use ChatMessage's name prop instead.
metadataReactNodeMetadata content rendered below the bubble, aligned with bubble text padding. Use on the last bubble in a message. Non-rendering scalar values (null, undefined, booleans, and the empty string) omit the aligned wrapper; numeric 0 remains aligned. If the last content is raw (no bubble), use ChatMessage's metadata prop instead.
group'first' | 'middle' | 'last'Position within a multi-bubble group. Controls corner radius reduction on the sender side. Leave unset for standalone bubbles (full radius).
widthSizeValueWidth of the bubble (number = pixels, string = used as-is). When set, replaces the default max(80%, 280px) width cap. Combine with variant="ghost" to let custom content (an artifact card, attachments) span the full message column.

Chat Message List

Presentational message container with density context and infinite scroll support. Provides role="log" with aria-live="polite" for accessibility. A flex spacer pushes messages to the bottom when the list isn't full; set align="top" to start messages at the top instead.

PropTypeDefaultDescription
children *ReactNodeMessage elements: typically ChatMessage or ChatSystemMessage.
emptyStateReactNodeContent shown when the list has no messages.
scrollToTopAction() => Promise<void>Async action fired when user scrolls to top. Use for loading older messages. Wrapped in useTransition: shows a spinner at the top while pending.
density'compact' | 'balanced' | 'spacious''balanced'Visual density: flows to child messages via context.
gap0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10Gap between top-level message rows. Defaults to the selected density; override for LLM event streams or independent rows that need different spacing from density.
align'top' | 'bottom''bottom'Vertical alignment when the list is shorter than its container. 'bottom' fills free space with a spacer so short conversations sit above the composer; 'top' omits the spacer so messages start at the top. Only affects a non-full list; overflowing lists scroll identically, preserving auto-scroll-to-bottom.
isStreamingbooleanfalseWhether an assistant message is actively streaming. Marks the log aria-busy so screen readers wait and announce the completed message once instead of re-announcing partial text on every token.

Chat Message Metadata

Place ChatMessageMetadata below a message or in the last ChatMessageBubble metadata slot to show a timestamp, footer content, and optional delivery status. Non-rendering scalar slots (booleans and empty strings) create no separator; numeric zero remains visible. Composite React content remains caller-owned.

PropTypeDefaultDescription
timestampReactNodeTimestamp content: a string or Timestamp component.
footerReactNodeFooter content: model info, reaction buttons, copy button.
status'sending' | 'sent' | 'delivered' | 'read' | 'error'Message delivery status. Shows icon + label.

Usage guidance

  • Put metadata in the last bubble metadata slot when the message uses bubbles, so it follows the bubble content.
  • Pass timestamp and footer content independently; separators are omitted for non-rendering scalar slots. Composite React nodes are used as supplied.
  • Avoid: Do not supply footer actions without their own accessible names and interaction behavior; the metadata row does not own those controls.
  • Avoid: Do not put the same metadata on both a bubble and its enclosing message.

Anatomy

  • Metadata row: Sender-aware line containing the visible metadata items.
  • Timestamp: Caller-supplied time content at the start of the logical sequence.
  • Footer: Caller-supplied information or actions between time and status.
  • Status: Localized delivery icon and text when a status is supplied.

Chat Send Button

Use ChatSendButton as ChatComposer’s send control when the action should switch between sending and stopping. Inside ChatComposer it reads readiness, streaming state, and action callbacks from context; standalone usage supplies those values explicitly.

PropTypeDefaultDescription
isStopShownbooleanWhether the stop button is shown. Defaults to context value.
isDisabledbooleanWhether the send button is disabled. Defaults to !canSend from context.
onSend() => voidCalled when the user clicks send. Defaults to context onSubmit.
onStop() => voidCalled when the user clicks stop during streaming. Defaults to context onStop.
sendIconReactNodeCustom icon for the send state. Defaults to arrowUp from icon registry.
stopIconReactNodeCustom icon for the stop state. Defaults to stop from icon registry.
size'sm' | 'md''md'Button size.
xstyleStyleXStylesStyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value: not an inline style object like style={{}}.

Usage guidance

  • Place ChatSendButton inside ChatComposer when its readiness and send or stop lifecycle should follow composer context.
  • For standalone use, provide the state and matching onSend or onStop callback explicitly.
  • Keep the translated Send or Stop accessible name intact when supplying custom icon artwork; the icon is decorative and must not be the only name source.
  • Avoid: Do not render the stop state without a working onStop callback from props or ChatComposer context.
  • Avoid: Do not expect isDisabled to disable the stop action; it applies only to the send state.

Anatomy

  • Action button: Circular icon-only Button that exposes the translated Send or Stop name and carries the chat-send-button theme target.

Chat System Message

Use ChatSystemMessage for concise, non-sender content inside a chat transcript. Choose the default variant for factual status notices and the divider variant for date or section breaks. Long default content wraps within the available width. The component exposes status semantics and the divider branch includes a labelled separator.

PropTypeDefaultDescription
children *ReactNodeSystem message content: factual text or React content such as a date, join/leave notice, or status change. Long default content wraps within the available width.
variant'default' | 'divider''default'Visual variant. 'default' renders centered text. 'divider' adds horizontal lines on each side via Divider: use for date separators and section breaks.
iconReactNodeOptional caller-provided icon content. Rendered before the message in the default variant; the divider variant currently does not render it. Wrap in Icon for consistent sizing.
xstyleStyleXStylesStyleX styles for layout customization. Must be a stylex.create() value: not an inline style object like style={{}}.

Usage guidance

  • Keep the visible message concise and self-contained so it remains understandable without an icon.
  • Use the divider variant for temporal or section boundaries and the default variant for factual status notices.
  • Avoid: Use ChatSystemMessage for sender-authored content; use ChatMessage and ChatMessageBubble instead.
  • Avoid: Use caller-provided icon artwork as the only source of meaning; the visible message must carry the same information.

Theming API

{
  "targets": [
    {
      "className": "astryx-chat-system-message",
      "visualProps": [
        "variant"
      ]
    }
  ]
}

Anatomy

  • System message: The noninteractive status row that carries the chat-system-message theme target.
  • Content: The concise caller-provided message or divider label.
  • Icon content: Rendered before the message in the default variant; the divider variant currently does not render it.
  • Divider: A labelled horizontal Divider rendered by the divider variant.

Chat Tokenized Text

Use ChatTokenizedText inside a chat message when stored plain text contains serialized token values that should be projected as inline badges or caller-rendered token content. Unmatched text is preserved exactly and empty token values are ignored so rendering always finishes.

PropTypeDefaultDescription
children *stringThe plain text message containing serialized token values. Patterns matching a token's value are replaced with inline token content.
tokensChatComposerToken[]Token definitions using the same type as composer input. Each non-empty value is matched literally. Empty values are ignored. Structured tokens render a Badge, while custom tokens render caller content.
xstyleStyleXStylesStyleX styles for the root text wrapper. Must be a stylex.create() value: not an inline style object like style={{}}.

Usage guidance

  • Reuse the same token definitions for composer insertion and message rendering so serialized values and visible labels stay aligned.
  • Use non-empty, stable serialized values so every definition identifies real message text.
  • Avoid: Use token labels as a substitute for the surrounding message text; the complete message must remain understandable in reading order.
  • Avoid: Put component-owned interaction or accessibility behavior inside a custom token renderer; custom token content remains caller owned.

Theming API

{
  "targets": [
    {
      "className": "astryx-chat-tokenized-text"
    }
  ]
}

Anatomy

  • Tokenized text: The inline root that preserves unmatched text and carries the chat-tokenized-text theme target.
  • Rendered token: Inline token content rendered by Badge for structured tokens or by the caller for custom tokens.

Chat Tool Calls

ChatToolCalls displays tool or function call invocations from an LLM response. Pass an array of calls and the component handles the rest: a single call renders inline, while multiple calls collapse into a summary with the latest call visible at the surface. Use it anywhere an AI agent shows what actions it took.

PropTypeDefaultDescription
calls *ChatToolCallItem[]Array of tool call data. Each item has name, status, target, duration, node, additions, deletions, stats, errorMessage, resultDetail, key, and data. status is one of 'pending', 'running', 'complete', or 'error' (defaults to 'complete').
labelstringCustom summary label shown for an expanded group. The translated call count is used when omitted.
isExpandedbooleanControlled expanded state for the group.
defaultIsExpandedbooleanfalseDefault expanded state when uncontrolled.
onExpandedChange(isExpanded: boolean) => voidCallback fired when the expanded state changes.
xstyleStyleXStylesStyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value, not an inline style object like style={{}}.

Usage guidance

  • Include a target string on every call so the user can see what the tool acted on: a file path, a shell command, or a search query.
  • Show a duration on completed calls so users can judge which tools are slow and understand why a response took time.
  • Provide a group label when the automatic count does not explain what the calls represent.
  • Provide resultDetail with a code block for calls that produce output (diffs for edits, terminal output for shell commands) so users can inspect results inline.
  • Set a unique key on each call item when streaming so React can animate additions without re-mounting completed rows.
  • Avoid: Don't omit the status field. Without it the call defaults to complete, which is misleading for calls that are still running or have failed.
  • Avoid: Don't display tool calls outside a chat message context; they are designed to sit inside an assistant message, not as standalone UI.
  • Avoid: Don't use custom wrappers around individual calls; the component handles single vs. grouped layout automatically based on the array length.

Anatomy

  • Status icon: A themed semantic success/error icon, or a spinner while the call is pending or running.
  • Tool name: The function or tool name displayed in monospace: bash, edit, read, web_search, etc.
  • Node badge: A neutral pill badge showing which sandbox or environment ran the tool, like cli:remote-server or workspace.
  • Target label: The target of the action (a file path, command, or search query) shown after the tool name.
  • Diff stats: Green additions and red deletions counts for edit operations, displayed inline after the target.
  • Duration: Execution time shown on the trailing edge for completed calls.
  • Group header: A wrench icon with a call count, shown when multiple calls are present. Clicking toggles between the summary and the full list.

Examples

  • Chat Composer Drawer: Composer drawer with file tokens, a collapsible toggle, and header actions. Use as a starting point for any chat composer with attachments.
  • Chat Composer Input:
  • Chat Composer:
  • Chat Dictation Button: Interactive dictation button connected to the SpeechRecognition API via useChatDictation. Click the mic to dictate into the composer.
  • Chat Layout Scroll Button: Scroll button in hidden, visible, and expanded (with label) states. The button fades in when the user scrolls up and expands when new messages arrive.
  • Chat Layout:
  • Chat Message Bubble: Grouped user bubbles with filled styling and a ghost-variant agent response, with timestamps and delivery status.
  • Chat Message List: Basic AI chat conversation with user and assistant messages. The simplest way to render a message list with alternating sender bubbles, metadata, and a date divider.
  • Chat Message Metadata: Three-message conversation showcasing error status with retry, delivery status, and full footer actions with model label.
  • Chat Message: A user multi-bubble group with delivery status and an assistant ghost response with avatar, name, timestamp, and model info.
  • Chat Send Button: Ready, custom icon, and streaming states of the send button.
  • Chat System Message: System messages for date dividers, status updates, and informational notices. Shows both default and divider variants in a realistic conversation flow.
  • Chat Tokenized Text:
  • Chat Tool Calls:
  • ChatComposer — Attachments: Chat composer with removable file tokens in a collapsible drawer. Use when users can attach files or context to their message.
  • ChatComposerDrawer — Attachments: Drawer with two rows: a scrollable carousel of image thumbnails and a row of removable file tokens. Omit count to keep the drawer always expanded.
  • ChatComposerDrawer — Collapsible: Drawer with many items and a collapse toggle. Pass count to enable the toggle; collapsed state shows a badge with the total count and a label.
  • ChatComposerDrawer — Feedback: Chat composer drawer with a feedback prompt and selectable lettered options. Use for user confirmation workflows that require explicit action before proceeding.
  • ChatComposerDrawer — With Progress: Drawer paired with a context progress bar in the header. Show context window usage when attachments consume part of the available token budget.
  • ChatComposer — Flat: The composer is raised by default (elevation="low"). Set elevation="none" to flatten it; it then draws a border with the same rest / hover / focus treatment as a text input.
  • ChatComposer — Footer Actions: Chat composer with dropdown menus for a model selector and settings in the footer, and a mic button in the send actions slot.
  • ChatComposer — Full Featured: Chat composer with all slots populated: collapsible attachment drawer, header actions, context progress bar, footer dropdown menus, and mic button. Shows the maximum composer configuration.
  • ChatComposerInput — Controlled: Controlled chat input with live value display. Use controlled mode when you need to read or transform the input value outside the composer.
  • ChatComposerInput — Disabled: Composer in a disabled state. Use when the input should be visible but not interactive, such as during streaming or when a prerequisite is unmet.
  • ChatComposerInput — Mentions: Chat input with an @ trigger that opens a typeahead menu for mentioning users. Selected names appear as inline tokens.
  • ChatComposerInput — Multiple Triggers: Chat input with both @ mentions and / commands. Each trigger type renders tokens in a distinct color so users can tell them apart at a glance.
  • ChatComposerInput — Slash Commands: Chat input with a / trigger for command selection. Use for AI assistants or bots that support structured commands.
  • ChatComposer — Simple: Minimal chat composer with a placeholder and submit handler. The simplest way to drop a message input into a page.
  • ChatComposer — Streaming: Chat composer with streaming state and a stop button. Use when the assistant is generating a response and the user can cancel.
  • ChatComposer — Validation: Chat composer with error and warning status messages. Status can appear above or below the composer to surface validation or system feedback.
  • ChatDictationButton — Basic: A dictation button wired to useChatDictation and placed in the sendActions slot of a ChatComposer. Click the microphone to transcribe speech into the input.
  • ChatDictationButton — In Composer: Dictation button placed in the sendActions slot of a chat composer. Shows the recommended integration point for voice input alongside the send button.
  • ChatDictationButton — States: Dictation button in idle, listening, and speaking states side by side. Shows the three visual phases of a voice input interaction.
  • ChatDictationButton — Sizes: Small and medium dictation buttons side by side. Use small in compact composer densities and medium for standard layouts.
  • ChatLayout — Panel View: Narrow sidebar chat in a constrained container that triggers compact density. Use for side panels, drawers, or embedded chat widgets where horizontal space is limited.
  • ChatLayoutScrollButton — Labels: Scroll button with different labels for context-specific notifications like new messages, unread replies, or a generic scroll prompt.
  • ChatMessage — Avatar & Name: Messages with avatars and sender names. Place the name on the bubble when using bubbles, or on the message wrapper for raw content.
  • ChatMessageBubble — Custom Content: Custom in-message content aligned to the bubble text column. An artifact card is wrapped in a ghost bubble with width="100%", so its left edge matches the bubble text and it spans the full message column instead of the default bubble width cap; the timestamp rides the bubble metadata slot.
  • ChatMessageBubble — Density: Compact, balanced, and spacious density modes side by side. Density controls bubble padding, corner radius, and spacing between grouped bubbles.
  • ChatMessageBubble — Grouping: Multi-bubble messages using first, middle, and last group positions. Grouped bubbles tighten corner radius on the sender side for a continuous visual flow.
  • ChatMessageBubble — Metadata: Bubbles with name and metadata slots aligned to bubble padding. Put name on the first bubble and metadata on the last bubble in a message.
  • ChatMessageBubble — Variants: Filled and ghost bubble variants for both user and assistant senders. Use filled for standard messages and ghost when content needs alignment without a visual boundary.
  • ChatMessage — Ghost: Ghost variant for messages without visible bubble boundaries. Keeps padding for alignment but renders a transparent background, useful for AI-style responses.
  • ChatMessageList — Density: Side-by-side comparison of compact, balanced, and spacious densities. Use compact in sidebars or panels, balanced for most full-page chat, and spacious for long-form reading. Use gap when row spacing needs to differ from density.
  • ChatMessageList — Full Featured: Conversation showcasing system messages, multi-bubble grouping, markdown, code blocks, and metadata. Combines date dividers, ghost bubbles, grouped messages, and rich content in a single example.
  • ChatMessageMetadata — Footer Actions: Assistant message with footer actions: copy, retry, thumbs up/down, and model label. Use for AI responses that need feedback or utility controls.
  • ChatMessageMetadata — Status: All 5 delivery statuses (sending, sent, delivered, read, and error), each with a timestamp. Use to show message delivery progress or surface failures.
  • ChatMessageMetadata — Timestamps: Timestamp-only metadata on user and assistant messages. Supports absolute time and relative formats via Timestamp.
  • ChatMessage — Multi-Bubble: Grouped bubbles using the group prop for corner radius reduction. Use first, middle, and last to visually connect related bubbles from the same sender.
  • ChatSendButton — Custom Icon: Send buttons with custom icons via sendIcon and stopIcon props. Use to match the personality of the chat experience: a paper airplane for messaging, sparkles for AI generation, or a check mark for confirmation flows.
  • ChatSendButton — In Composer: Send button inside ChatComposer, where it reads state from context automatically. No wiring needed; the button enables when the input has content.
  • ChatSendButton — States: Disabled, ready, and streaming states at both sizes. The button automatically toggles between send (primary) and stop (secondary) based on streaming state.
  • ChatSystemMessage — Status Updates: Realistic status messages in a conversation flow showing membership changes, timestamps, and resolution notices.
  • ChatSystemMessage — Variants: Default and divider variants side by side. Use default for inline status updates and divider for date separators or section breaks.
  • ChatSystemMessage — Icon: System messages with a leading icon that reinforces the message type. Use icons to help users scan and identify message categories at a glance.
  • ChatTokenizedText — Basic: A message with @mention tokens. Each matching pattern is replaced with its display name badge.
  • ChatTokenizedText — Colors: Tokens with different color variants to distinguish mentions, bugs, and features. Use variant colors to create a visual taxonomy: blue for people, red for bugs, green for features.
  • ChatToolCalls — Expandable: Tool calls with expandable result details showing diffs and command output in code blocks. Click a row to reveal its result.
  • ChatToolCalls — Statuses: All four status states (pending, running, complete, and error) shown together in a single group.
  • ChatToolCalls — Simple: A single inline tool call above a collapsible multi-call group with diff stats. Shows both layouts side by side.

Complete example dependencies

Some examples import optional icons. The per-example install command in the examples list identifies exactly what that example needs. For all examples on this page:

npm install @heroicons/react@2.2.0