Components
AgentMessage
A whole assistant message: reasoning, streaming markdown, tool calls, approvals and sources.
Example
I’ll check the route handler and confirm the limiter API first.
Interactive, and the same on the components page. Try the keyboard below on it.
Installation
npm i @dgesteves/agent-ui-kit aiimport { AgentMessage } from '@dgesteves/agent-ui-kit';
// Once per app (with Tailwind v4: @import '@dgesteves/agent-ui-kit/tailwind.css'; in your CSS)
import '@dgesteves/agent-ui-kit/styles.css';npx shadcn@latest add @agent-ui-kit/agent-messageimport { AgentMessage } from '@/components/agent-ui/agent-message';The styles are once per app; Getting started has the Tailwind v4 and plain CSS options.
Usage
const { messages, status, addToolApprovalResponse } = useChat();
const last = messages.findLast((m) => m.role === 'assistant');
{last && (
<AgentMessage
message={last}
streaming={status === 'streaming'}
tools={{ run_command: { label: 'Run command', risk: 'high' } }}
onToolApproval={addToolApprovalResponse}
/>
)}API reference
Generated from the types the package ships, so it matches the version you install.
AgentMessage
Renders an assistant UIMessage part by part: streaming markdown, collapsible
reasoning, consecutive tool calls grouped into a timeline (with inline approval
cards), files, and the message's sources with linked inline citations.
Props
messagerequiredPick<UIMessage, 'id' | 'role' | 'parts'>streamingbooleanDefaultfalseThe message is still being generated. Text parts with an explicit
statetake precedence.activebooleanDefaulttrueWhether the run can still make progress, including while it waits on the user. Pass
falseonce it has ended (stopped, failed, or an older message): tool calls that never settled read "Stopped" instead of running forever. SeeToolCallTimeline'sactive.toolsRecord<string, ToolMeta>renderTool(part: ToolPart) => ReactNode | undefinedRender a tool part yourself. Return
undefinedto use the default timeline, ornullto render nothing. Custom-rendered parts split the timeline.onToolApproval(response: ToolApprovalResponse) => void | PromiseLike<void>Enables inline approval cards. Pass
useChat().addToolApprovalResponse.approvalPropsPartial<Omit<ApprovalCardProps, 'toolName' | 'status' | 'onApprove' | 'onDeny'>>Props forwarded to every approval card, e.g.
{ autoFocus: true }.renderData(part: DataPart) => ReactNodeRender
data-*parts. They are skipped when omitted.showSourcesbooleanDefaulttruesourcesVariant'chips' | 'cards'Default'chips'allowedImageHostsreadonly string[]Where images in text, reasoning and image file parts may load from: host names,
'self'for relative URLs, or'*'for every image. Default: none, so other images render as links. File parts withdata:andblob:URLs always preview. SeeMarkdownProps.allowedImageHosts.timingsToolTimingsExternally measured tool timings. Measured client-side when omitted.
Other props go to the root <article>: className (merged with tailwind-merge), id, aria-*, data-* and event handlers.
ToolMeta
Per-tool presentation. Every field is optional.
labelstringDisplay label. Defaults to the humanized tool name.
iconReactNodesummary(input: unknown, part: ToolPart) => ReactNodeOne-line summary shown next to the label. Defaults to the most descriptive input field. Not called before any input has arrived; while the input streams it can be partial.
renderOutput(output: unknown, part: ToolPart) => ReactNodeReplace the default JSON output view.
riskRiskLevel | ((input: unknown) => RiskLevel)Risk shown on approval requests for this tool.
ToolApprovalResponse
idrequiredstringapprovedrequiredbooleanreasonstring
Accessibility
| Keys | Action |
|---|---|
| ↑↓ | Move between tool calls in a timeline |
| YN | Approve or deny, with focus in an approval card |
- The message is an
<article>witharia-busywhile it streams, so screen readers wait for the text to settle. - Each part keeps its own behavior: the tool timeline, approval cards, reasoning and sources below all apply.
- Images in model output don't load unless
allowedImageHostsallows them; a blocked image is a link with its alt text, read as "Image:". - Citation markers such as
[2]become links named "Source 2" to the matching source.
Theming
Every color, radius and font is a CSS variable, so the overrides in Theming apply, on :root or scoped to any element. These hooks are read from the component's markup.
- Slots (data-slot)
- agent-message
- State attributes
- data-roleon
agent-messagethe message’s role: assistant, user or system - Tokens it uses
- --aui-border--aui-fg--aui-fg-subtle--aui-font-sans--aui-ring--aui-surface--aui-surface-2
/* Only this component, and only inside .settings-panel */
.settings-panel [data-slot='agent-message'] {
--aui-radius: 4px;
}