Skip to main content
Urbicon UI

ChatMessageexperimental

Renders one chat message and its ordered parts: markdown text, reasoning, tool calls, attachments, and a citation footer, with copy and regenerate actions and per-role styling.

Playground

Change the layout, reply role, and density to see how a message renders. The bubble layout tints and aligns by role; plain is a full-width column. Hover a message to reveal its copy and regenerate bar. The live playground shows a full streaming conversation.

How do I center an element with flexbox?

Copy

Make the parent a flex container and center on both axes:

  1. justify-content: center sets the horizontal position

  2. align-items: center sets the vertical position

The child then sits in the middle of the container.

Copy Regenerate
Layout Style variant
Density Style variant
Reply role
<script lang="ts">
  import { ChatMessage } from '@urbicon-ui/blocks';

  const thread = [
    {
      id: 'pg-question',
      role: 'user',
      parts: [{ type: 'text', text: 'How do I center an element with flexbox?' }],
      createdAt: new Date('2026-01-01T09:40:00.000Z'),
      status: 'complete'
    },
    {
      id: 'pg-reply',
      role: 'assistant',
      parts: [
        {
          type: 'text',
          text: `Make the parent a flex container and center on both axes:

1. \`justify-content: center\` sets the horizontal position
2. \`align-items: center\` sets the vertical position

The child then sits in the **middle** of the container.`
        }
      ],
      createdAt: new Date('2026-01-01T09:41:00.000Z'),
      status: 'complete'
    }
  ];

  function regenerate() {
    // re-run the last assistant turn
  }
</script>

{#each thread as message (message.id)}
  <ChatMessage
    {message}
    onRegenerate={message.role === 'assistant' ? regenerate : undefined}
  />
{/each}

01 Examples

An agentic message

A message with reasoning, a tool call, answer text, and two sources, rendered in part order. The reasoning collapses into a 'Thought for 2s' disclosure, the tool call shows its settled state, and the two sources form the citation footer with inline [1] / [2] chips.
Assistant

The user wants sources. Look up the attention paper and the scaling-laws work, then cite both inline.

Input
{
  "query": "transformer attention scaling laws"
}
Output
{
  "hits": 2
}

The Transformer replaced recurrence with self-attention , and later work showed its performance scales predictably with compute .

Copy Regenerate
<script lang="ts">
  import { ChatMessage, type ChatMessageData } from '@urbicon-ui/blocks';

  // A single assistant message whose ordered parts exercise the whole dispatch:
  // reasoning → tool-call → text, with two sources collected into the footer.
  const message: ChatMessageData = {
    id: 'agentic-1',
    role: 'assistant',
    parts: [
      {
        type: 'reasoning',
        text: 'The user wants sources. Look up the attention paper and the scaling-laws work, then cite both inline.',
        durationMs: 2400
      },
      {
        type: 'tool-call',
        id: 'tc-search',
        name: 'search_papers',
        state: 'complete',
        input: { query: 'transformer attention scaling laws' },
        output: { hits: 2 }
      },
      {
        type: 'text',
        text: 'The Transformer replaced recurrence with self-attention [1], and later work showed its performance scales predictably with compute [2].'
      },
      {
        type: 'source',
        id: '1',
        title: 'Attention Is All You Need',
        url: 'https://arxiv.org/abs/1706.03762',
        snippet: 'We propose a new simple network architecture, the Transformer.'
      },
      {
        type: 'source',
        id: '2',
        title: 'Scaling Laws for Neural Language Models',
        url: 'https://arxiv.org/abs/2001.08361',
        snippet: 'Performance improves smoothly with model size, data and compute.'
      }
    ],
    createdAt: new Date('2026-01-01T09:41:00'),
    status: 'complete'
  };
</script>

<div class="w-full max-w-2xl">
  <ChatMessage {message} layout="plain" onRegenerate={() => {}} />
</div>

Custom tool-call renderer

partRenderers overrides the rendering for one part type, keyed by type, and leaves the others unchanged. Here a compact status pill replaces the default tool-call card; the snippet receives the fully typed tool-call part.
Assistant
get_weather → complete

It's 7 °C and overcast in Berlin right now.

Copy
<script lang="ts">
  import { ChatMessage, type ChatMessageData, type ChatToolCallPart } from '@urbicon-ui/blocks';

  const message: ChatMessageData = {
    id: 'custom-tool-1',
    role: 'assistant',
    parts: [
      {
        type: 'tool-call',
        id: 'tc-weather',
        name: 'get_weather',
        state: 'complete',
        input: { city: 'Berlin' },
        output: { tempC: 7, condition: 'Overcast' }
      },
      { type: 'text', text: "It's **7 °C** and overcast in Berlin right now." }
    ],
    createdAt: new Date('2026-01-01T09:41:00'),
    status: 'complete'
  };
</script>

<!--
  partRenderers swaps the built-in tool-call presentation for your own, keyed by
  the part `type`. The snippet receives the fully-typed tool-call part.
-->
{#snippet toolCall(part: ChatToolCallPart)}
  <div
    class="border-border-subtle bg-surface-base rounded-modify flex items-center gap-2 border px-3 py-2 text-sm"
  >
    <span class="bg-success size-2 rounded-full"></span>
    <span class="text-text-primary font-medium">{part.name}</span>
    <span class="text-text-tertiary">{part.state}</span>
  </div>
{/snippet}

<div class="w-full max-w-2xl">
  <ChatMessage {message} layout="plain" partRenderers={{ 'tool-call': toolCall }} />
</div>

Error state with retry

A message with status='error' keeps the text that already streamed in and shows an alert below it. Pass onRetry to render the Retry button; aborted messages use the same handler.
Assistant

Let me pull the latest figures for you

Copy

Retry pressed 0×

<script lang="ts">
  import { ChatMessage, type ChatMessageData } from '@urbicon-ui/blocks';

  // `status: 'error'` switches the message to its failure presentation: the
  // partial text stays, and an Alert appears with a Retry button wired to onRetry.
  let attempt = $state(1);

  const message: ChatMessageData = {
    id: 'error-1',
    role: 'assistant',
    parts: [{ type: 'text', text: 'Let me pull the latest figures for you' }],
    createdAt: new Date('2026-01-01T09:41:00'),
    status: 'error'
  };
</script>

<div class="w-full max-w-2xl">
  <ChatMessage
    {message}
    layout="plain"
    onRetry={() => (attempt += 1)}
    errorLabel="Couldn't reach the model"
  />
  <p class="text-text-tertiary mt-2 text-xs">Retry pressed {attempt - 1}×</p>
</div>

02 Part dispatch

A message is an ordered list of parts. ChatMessage renders them in order, choosing a renderer for each type. The same component shows a plain answer or a full agentic transcript.

  • text → rendered through StreamingMarkdown, with links checked against the URL policy.
  • reasoning → a collapsed ReasoningDisclosure with a "Thought for Xs" label.
  • tool-call → a ToolCallCard reflecting its pending / running / complete / error state.
  • attachment → a policy-checked chip linking to the file as a download, not inline media.
  • source → moved into the deduplicated citation footer and numbered as [n] markers.

Override any part type except source via partRenderers, and replace the avatar, action bar, or metadata row through their snippets. ChatMessage never mutates the message you pass it.

03 Accessibility

Labelled actions

The copy and regenerate buttons carry aria-labels (copyLabel, regenerateLabel) and are wrapped in tooltips. They live in a bar revealed on hover / focus-within. Keyboard users reach them by tabbing; the reveal is visual only and does not trap focus.

Copy feedback

A successful copy is announced through a visually hidden role="status" region (the copiedLabel text), so screen-reader users get the confirmation even without a visible toast.

Error & aborted alerts

status: 'error' and 'aborted' render through the Alert primitive, so the failure is exposed with the correct alert semantics rather than styled text alone.

Decorative avatar & time

The role avatar is decorative and hidden from assistive tech; the timestamp renders as a <time datetime> element, so the exact instant is machine-readable alongside the visible label.

04 API Reference

24 props
24 props 1 required
Prop
Type
Default
Description

05 Types

Local type definitions used by this component.

12 types
Name
Kind
Category
Used by
Description

06 Installation

Import

import { ChatMessage } from '@urbicon-ui/blocks';
import type {
  ChatMessageData,
  ChatMessagePart,
  ChatToolCallPart,
  ChatReasoningPart,
  CitationSource
} from '@urbicon-ui/blocks';