XDSChatComposerInput@xds/core · Chat
Preview coming soon

Usage

XDSChatMessageList is the scrollable container for chat messages. It renders children in a flex column with role="log" for accessibility, provides density context to child messages, and supports infinite scroll for loading older messages. Use it inside XDSChatLayout for full-page chat with auto-scroll and composer docking, or standalone for embedded message panels.

Best practices

GuidancePractices
DoCompose messages using MessageList > Message > Bubble for consistent sender-aware styling and density.
DoSet the density prop to control spacing globally — compact for sidebars, balanced for most views, spacious for long-form reading. Individual messages can override.
DoUse the group prop on bubbles (first, middle, last) when a single sender sends multiple consecutive messages — it tightens corner radius to visually connect them.
DoUse XDSChatSystemMessage with variant="divider" for date separators and default for inline status notices like joins, leaves, or topic changes.
DoPut name on the first bubble and metadata on the last bubble in a message so they align with the bubble's inline padding.
DoProvide an emptyState prop so new users see a clear prompt to start a conversation instead of a blank screen.
DoUse the ghost bubble variant for AI-style responses that show rich content like code blocks or markdown without a visible boundary.
Don'tDon't use XDSChatSystemMessage for sender content — it has no avatar, alignment, or bubble. Use XDSChatMessage with a sender role instead.
Don'tDon'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.
Don'tDon't nest XDSChatMessage inside another XDSChatMessage — each message is a standalone article element with its own sender context.
Don'tDon't apply a fixed height directly on the message list — wrap it in a sized container and let the list fill with flex: 1.
Don'tDon't mix filled and ghost bubble variants within the same sender's messages — pick one style per side and use it consistently.
Don'tDon't place metadata or names on both the bubble and the message wrapper — pick one based on whether the content has a bubble boundary.

Anatomy

ElementDescription
Message arearequiredScrollable region for messages. Renders children (typically XDSChatMessageList) in a flex column that pushes content to the bottom when the list is short.
Frosted glass dockrequiredSticky or fixed container at the bottom with a backdrop-blur layer. Houses the scroll button and composer.
Scroll-to-bottom buttonAppears when the user scrolls up or new messages arrive. Defaults to XDSChatLayoutScrollButton; pass null to hide or a custom element to override.
ComposerrequiredThe input area for sending messages, typically XDSChatComposer. Docked at the bottom inside the frosted glass layer.
Empty stateCentered placeholder shown when no messages exist. Use XDSEmptyState for a consistent look.
AvatarA sender avatar rendered beside the message. Typically XDSAvatar with size="small". Hidden for system messages.
NameSender name above the message body. Place on the bubble when using bubbles, or on the message wrapper for raw content.
ContentrequiredThe message body — one or more XDSChatMessageBubble elements, or any free-form ReactNode like images or tool calls.
MetadataTimestamp, delivery status, and footer actions below the message. Place on the last bubble or on the message wrapper.

Import

ts
import {XDSChatComposerInput} from '@xds/core/Chat'

Props

PropTypeDescription
ref
React.Ref<XDSChatComposerInputHandle>Imperative handle for programmatic control — insertToken, insertText, focus, and getValue.
value
stringControlled input value. Pair with onChange for two-way binding.
onChange
(value: string) => voidCalled when the input value changes. The serialized string includes token placeholders.
placeholder
string (default: 'Type a message…')Placeholder text shown when the input is empty.
maxRows
number (default: 8)Maximum visible rows before the input scrolls. Use a lower value in compact layouts.
triggers
XDSChatComposerTrigger[]Trigger definitions for typeahead menus. Each trigger specifies a character (@ or /), a search source, and an onSelect handler that returns the token to insert.
debounceMs
number (default: 150)Debounce delay for async search sources to avoid excessive network requests.
hasHistory
boolean (default: true)Enable ArrowUp/Down to recall previously submitted messages.
label
string (default: 'Message input')Accessible label announced by screen readers.
isDisabled
boolean (default: false)Disables the input. Use during streaming or when a prerequisite is unmet.
onPaste
(event, text) => voidCalled when text is pasted. Use to intercept or transform pasted content.
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.

Examples

Common configurations, variations, and states.
ChatComposerInput — ControlledControlled chat input with live value display. Use controlled mode when you need to read or transform the input value outside the composer.
tsx
'use client';
import {useState} from 'react';
import {XDSChatComposer, XDSChatComposerInput} from '@xds/core/Chat';
import {XDSStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
export default function ChatComposerInputControlledInput() {
const [value, setValue] = useState('');
return (
<XDSStack direction="vertical" gap={3} style={{width: '100%', maxWidth: 450}}>
<XDSChatComposer
onSubmit={() => setValue('')}
value={value}
onChange={setValue}
input={
<XDSChatComposerInput
value={value}
onChange={setValue}
placeholder="Type a message..."
/>
}
/>
<XDSText type="supporting" color="secondary">
Value: {JSON.stringify(value)}
</XDSText>
</XDSStack>
);
}
ChatComposerInput — DisabledComposer in a disabled state. Use when the input should be visible but not interactive, such as during streaming or when a prerequisite is unmet.
tsx
'use client';
import {XDSChatComposer, XDSChatComposerInput} from '@xds/core/Chat';
import {XDSStack} from '@xds/core/Layout';
export default function ChatComposerInputDisabled() {
return (
<XDSStack direction="vertical" style={{width: '100%', maxWidth: 450}}>
<XDSChatComposer
onSubmit={() => {}}
isDisabled
input={
<XDSChatComposerInput isDisabled placeholder="Input is disabled" />
}
/>
</XDSStack>
);
}
ChatComposerInput — MentionsChat input with an @ trigger that opens a typeahead menu for mentioning users. Selected names appear as inline tokens.
tsx
'use client';
import {useState} from 'react';
import {
XDSChatComposer,
XDSChatComposerInput,
type XDSChatComposerTrigger,
} from '@xds/core/Chat';
import {createStaticSource} from '@xds/core/Typeahead';
import {XDSTypeaheadItem} from '@xds/core/Typeahead';
import type {XDSSearchableItem} from '@xds/core/Typeahead';
import {XDSStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
const USERS: XDSSearchableItem<{role: string}>[] = [
{id: 'cindy', label: 'Cindy Zhang', auxiliaryData: {role: 'Design Systems'}},
{id: 'alex', label: 'Alex Johnson', auxiliaryData: {role: 'Frontend'}},
{id: 'sam', label: 'Sam Rivera', auxiliaryData: {role: 'Backend'}},
{id: 'jordan', label: 'Jordan Lee', auxiliaryData: {role: 'Product'}},
];
const userSource = createStaticSource(USERS);
export default function ChatComposerInputMentionTrigger() {
const [value, setValue] = useState('');
const mentionTrigger: XDSChatComposerTrigger = {
character: '@',
searchSource: userSource,
renderItem: item => (
<XDSTypeaheadItem
item={item}
description={(item.auxiliaryData as {role: string})?.role}
/>
),
onSelect: item => ({
value: `@${item.id}`,
label: item.label,
variant: 'blue' as const,
}),
};
return (
<XDSStack direction="vertical" gap={3} style={{width: '100%', maxWidth: 450}}>
<XDSChatComposer
onSubmit={() => setValue('')}
input={
<XDSChatComposerInput
value={value}
onChange={setValue}
triggers={[mentionTrigger]}
placeholder="Type @ to mention someone..."
/>
}
/>
<XDSText type="supporting" color="secondary">
Value: {JSON.stringify(value)}
</XDSText>
</XDSStack>
);
}
ChatComposerInput — Multiple TriggersChat input with both @ mentions and / commands. Each trigger type renders tokens in a distinct color so users can tell them apart at a glance.
tsx
'use client';
import {useState} from 'react';
import {
XDSChatComposer,
XDSChatComposerInput,
type XDSChatComposerTrigger,
} from '@xds/core/Chat';
import {createStaticSource} from '@xds/core/Typeahead';
import type {XDSSearchableItem} from '@xds/core/Typeahead';
import {XDSStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
const USERS: XDSSearchableItem[] = [
{id: 'cindy', label: 'Cindy Zhang'},
{id: 'alex', label: 'Alex Johnson'},
{id: 'sam', label: 'Sam Rivera'},
{id: 'jordan', label: 'Jordan Lee'},
];
const COMMANDS: XDSSearchableItem[] = [
{id: 'summarize', label: 'summarize'},
{id: 'translate', label: 'translate'},
{id: 'search', label: 'search'},
{id: 'code', label: 'code'},
];
const userSource = createStaticSource(USERS);
const commandSource = createStaticSource(COMMANDS);
export default function ChatComposerInputMultipleTriggers() {
const [value, setValue] = useState('');
const mentionTrigger: XDSChatComposerTrigger = {
character: '@',
searchSource: userSource,
onSelect: item => ({
value: `@${item.id}`,
label: item.label,
variant: 'blue' as const,
}),
};
const commandTrigger: XDSChatComposerTrigger = {
character: '/',
searchSource: commandSource,
onSelect: item => ({
value: `/${item.label}`,
label: `/${item.label}`,
variant: 'yellow' as const,
}),
};
return (
<XDSStack direction="vertical" gap={3} style={{width: '100%', maxWidth: 450}}>
<XDSText type="supporting" color="secondary">
Type @ for mentions (blue) or / for commands (yellow)
</XDSText>
<XDSChatComposer
onSubmit={() => setValue('')}
input={
<XDSChatComposerInput
value={value}
onChange={setValue}
triggers={[mentionTrigger, commandTrigger]}
placeholder="Type @ or / ..."
/>
}
/>
<XDSText type="supporting" color="secondary">
Value: {JSON.stringify(value)}
</XDSText>
</XDSStack>
);
}
ChatComposerInput — Slash CommandsChat input with a / trigger for command selection. Use for AI assistants or bots that support structured commands.
tsx
'use client';
import {
XDSChatComposer,
XDSChatComposerInput,
type XDSChatComposerTrigger,
} from '@xds/core/Chat';
import {createStaticSource} from '@xds/core/Typeahead';
import {XDSTypeaheadItem} from '@xds/core/Typeahead';
import type {XDSSearchableItem} from '@xds/core/Typeahead';
import {XDSStack} from '@xds/core/Layout';
const COMMANDS: XDSSearchableItem<{description: string}>[] = [
{id: 'summarize', label: 'summarize', auxiliaryData: {description: 'Summarize the conversation'}},
{id: 'translate', label: 'translate', auxiliaryData: {description: 'Translate text to another language'}},
{id: 'search', label: 'search', auxiliaryData: {description: 'Search the web or documents'}},
{id: 'code', label: 'code', auxiliaryData: {description: 'Generate or explain code'}},
{id: 'help', label: 'help', auxiliaryData: {description: 'Show available commands'}},
];
const commandSource = createStaticSource(COMMANDS);
export default function ChatComposerInputSlashCommands() {
const commandTrigger: XDSChatComposerTrigger = {
character: '/',
searchSource: commandSource,
renderItem: item => (
<XDSTypeaheadItem
item={item}
description={(item.auxiliaryData as {description: string})?.description}
/>
),
onSelect: item => ({
value: `/${item.label}`,
label: `/${item.label}`,
variant: 'yellow' as const,
}),
};
return (
<XDSStack direction="vertical" style={{width: '100%', maxWidth: 450}}>
<XDSChatComposer
onSubmit={() => {}}
input={
<XDSChatComposerInput
triggers={[commandTrigger]}
placeholder="Type / for commands..."
/>
}
/>
</XDSStack>
);
}

Showcase source

tsx
'use client';
import {XDSChatComposer, XDSChatComposerInput} from '@xds/core/Chat';
import {XDSStack} from '@xds/core/Layout';
export default function ChatComposerInputShowcase() {
return (
<XDSStack direction="vertical" style={{width: '100%', maxWidth: 450}}>
<XDSChatComposer
onSubmit={() => {}}
input={
<XDSChatComposerInput placeholder="Ask me anything about XDS..." />
}
/>
</XDSStack>
);
}