Message

Single chat-message row scoped to one speaker (user / assistant / system / tool). Headless content slot — pair with Markdown, CodeBlock, or ToolCall children.

User → assistant turn

How do I wire up a tabbed settings panel that's keyboard-accessible?
Use the Tabs primitive — Radix handles arrow-key navigation, focus management, and the roving-tabindex pattern for you.

Composed assistant turn — markdown + reasoning + actions

Yes — pair Tabs with a labelled TabsList and one TabsContent per panel. The trigger value selects the panel with the same value.

<Tabs defaultValue="overview" aria-label="Settings">
  <TabsList>…</TabsList>
  <TabsContent value="overview">…</TabsContent>
</Tabs>

Installation

pnpm
pnpm dlx @hex-core/cli add message

Composed assistant turn

Markdown + ToolCall inside one assistant message.

tsx
<Message role="assistant">
  <Markdown>{response}</Markdown>
  <ToolCall name="search" state="result" result={hits} />
</Message>

Variant values

roleVisual treatment per speaker.
ValueDescription
user
Tinted secondary background — user turns.
assistantdefault
Card background — model output.
system
Muted, italic — system instructions.
tool
Accent left-border — tool messages distinct from assistant text.

API Reference

PropTypeDefaultDescription
rolerequired
"user" | "assistant" | "system" | "tool"Speaker. Drives variant styling and the data-role attribute.
childrenrequired
ReactNodeMessage content. Strings, Markdown, CodeBlock, ToolCall, or any composition.
className
stringAdditional CSS classes merged onto the row.

AI Guidance

When to use

Wrap every conversation turn — user, assistant, system, or tool. Pair with MessageList for the scrolling viewport. Compose Markdown, CodeBlock, and ToolCall as children for rich assistant turns.

When not to use

Don't use for non-conversational text (use Card or plain elements). Don't put streaming logic here — the consumer drives state, Message just renders.

Common mistakes

  • Adding streaming/fetch logic inside Message — keep it pure
  • Using `role="tool"` for assistant text that mentions a tool — `tool` is for the actual tool turn
  • Hard-coding markdown rendering inside Message — pass <Markdown>{...}</Markdown> as a child instead

Accessibility

Renders as a div with `data-role`. For screen-reader chat semantics, wrap MessageList in `role="log"` and consider `aria-live="polite"` on the streaming container.

Token budget: 220

Verified against @hex-core/components@1.12.0