---
title: Message
description: Chat/comment message bubble with author and timestamp
visibility: guest
draft: false
---

# Message

A chat message bubble with author metadata, timestamp, side-based alignment, and slots for reactions and attachments. Designed for chat interfaces and comment threads.

## Basic Usage

:::preview{title="Simple message"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::message :author="['name' => 'Alice']">
        Hey, how's the project going?
    </x-wirekit::message>
</div>
:::

:::source{language="blade"}
<x-wirekit::message :author="['name' => 'Alice']">
    Hey, how's the project going?
</x-wirekit::message>
:::

## With Timestamp

:::preview{title="Message with timestamp"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::message :author="['name' => 'Alice']" timestamp="2026-04-20 14:30:00">
        The new dashboard looks great!
    </x-wirekit::message>
</div>
:::

:::source{language="blade"}
<x-wirekit::message :author="['name' => 'Alice']" timestamp="2026-04-20 14:30:00">
    The new dashboard looks great!
</x-wirekit::message>
:::

## Side Alignment

Use `side="right"` for outgoing messages (current user):

:::preview{title="Left and right alignment"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::stack gap="sm">
        <x-wirekit::message :author="['name' => 'Alice']" timestamp="2026-04-20 14:30:00">
            Have you seen the latest designs?
        </x-wirekit::message>
        <x-wirekit::message :author="['name' => 'You']" side="right" timestamp="2026-04-20 14:31:00">
            Yes, they look amazing!
        </x-wirekit::message>
        <x-wirekit::message :author="['name' => 'Alice']" timestamp="2026-04-20 14:32:00">
            Great, I'll send the final files tomorrow.
        </x-wirekit::message>
    </x-wirekit::stack>
</div>
:::

:::source{language="blade"}
<x-wirekit::stack gap="sm">
    <x-wirekit::message :author="['name' => 'Alice']" timestamp="2026-04-20 14:30:00">
        Have you seen the latest designs?
    </x-wirekit::message>
    <x-wirekit::message :author="['name' => 'You']" side="right" timestamp="2026-04-20 14:31:00">
        Yes, they look amazing!
    </x-wirekit::message>
    <x-wirekit::message :author="['name' => 'Alice']" timestamp="2026-04-20 14:32:00">
        Great, I'll send the final files tomorrow.
    </x-wirekit::message>
</x-wirekit::stack>
:::

## With Avatar

:::preview{title="Message with avatar"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::message :author="['name' => 'Alice', 'avatar' => '/placeholder/40x40?bg=4f46e5&fg=ffffff&label=A&gradient=none']" timestamp="2026-04-20 10:00:00">
        Good morning everyone!
    </x-wirekit::message>
</div>
:::

:::source{language="blade"}
<x-wirekit::message :author="['name' => 'Alice', 'avatar' => '/placeholder/40x40?bg=4f46e5&fg=ffffff&label=A&gradient=none']" timestamp="2026-04-20 10:00:00">
    Good morning everyone!
</x-wirekit::message>
:::

## Author Role Badge

:::preview{title="Author with role"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::message :author="['name' => 'Alice', 'role' => 'Admin']">
        Welcome to the team channel.
    </x-wirekit::message>
</div>
:::

:::source{language="blade"}
<x-wirekit::message :author="['name' => 'Alice', 'role' => 'Admin']">
    Welcome to the team channel.
</x-wirekit::message>
:::

## Edited Indicator

:::preview{title="Edited message"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::message :author="['name' => 'Bob']" :edited="true">
        This message was updated after posting.
    </x-wirekit::message>
</div>
:::

:::source{language="blade"}
<x-wirekit::message :author="['name' => 'Bob']" :edited="true">
    This message was updated after posting.
</x-wirekit::message>
:::

## System Messages with Intent

Use intent variants for system-generated messages:

:::preview{title="Intent variants"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::stack gap="sm">
        <x-wirekit::message :author="['name' => 'System']" intent="info">
            Alice joined the channel.
        </x-wirekit::message>
        <x-wirekit::message :author="['name' => 'System']" intent="success">
            Deployment completed successfully.
        </x-wirekit::message>
        <x-wirekit::message :author="['name' => 'System']" intent="warning">
            Rate limit approaching: 80% used.
        </x-wirekit::message>
    </x-wirekit::stack>
</div>
:::

:::source{language="blade"}
<x-wirekit::stack gap="sm">
    <x-wirekit::message :author="['name' => 'System']" intent="info">
        Alice joined the channel.
    </x-wirekit::message>
    <x-wirekit::message :author="['name' => 'System']" intent="success">
        Deployment completed successfully.
    </x-wirekit::message>
    <x-wirekit::message :author="['name' => 'System']" intent="warning">
        Rate limit approaching: 80% used.
    </x-wirekit::message>
</x-wirekit::stack>
:::

## With Reactions

:::preview{title="Message with reactions"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::message :author="['name' => 'Alice']" timestamp="2026-04-20 14:30:00">
        We shipped the new feature! 🚀
        <x-slot:reactions>
            <x-wirekit::reaction emoji="🎉" :count="5" :active="true" />
            <x-wirekit::reaction emoji="👍" :count="3" />
            <x-wirekit::reaction emoji="❤️" :count="2" />
        </x-slot:reactions>
    </x-wirekit::message>
</div>
:::

:::source{language="blade"}
<x-wirekit::message :author="['name' => 'Alice']" timestamp="2026-04-20 14:30:00">
    We shipped the new feature! 🚀
    <x-slot:reactions>
        <x-wirekit::reaction emoji="🎉" :count="5" :active="true" />
        <x-wirekit::reaction emoji="👍" :count="3" />
        <x-wirekit::reaction emoji="❤️" :count="2" />
    </x-slot:reactions>
</x-wirekit::message>
:::

## With File Attachments

Attach files inline beneath the message text. The pattern below renders a vertical stack of clickable rows, each with an icon, file name, and human-readable size — the same shape Slack, Discord, and Linear use for attached documents:

:::preview{title="Message with two file attachments"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::message :author="['name' => 'Marcus']" timestamp="2026-04-22 09:14:00">
        Drafted the brand guidelines and a first pass at the typography spec — let me know what you think before EOD.
        <x-wirekit::stack gap="xs" style="margin-top: 0.5rem;">
            <a href="#" style="display: flex; align-items: center; gap: 0.625rem; padding: 0.5rem 0.75rem; border: 1px solid var(--color-wk-border); border-radius: var(--radius-wk-md); background: var(--color-wk-bg-elevated); text-decoration: none; color: var(--color-wk-text);">
                <x-wirekit::icon name="download" style="width: 1.25rem; height: 1.25rem; color: var(--color-wk-text-muted); flex-shrink: 0;" />
                <span style="display: flex; flex-direction: column; min-width: 0; flex: 1;">
                    <span style="font-size: 0.875rem; font-weight: 500;">brand-guidelines-v3.pdf</span>
                    <span style="font-size: 0.75rem; color: var(--color-wk-text-muted);">2.4 MB · PDF</span>
                </span>
            </a>
            <a href="#" style="display: flex; align-items: center; gap: 0.625rem; padding: 0.5rem 0.75rem; border: 1px solid var(--color-wk-border); border-radius: var(--radius-wk-md); background: var(--color-wk-bg-elevated); text-decoration: none; color: var(--color-wk-text);">
                <x-wirekit::icon name="download" style="width: 1.25rem; height: 1.25rem; color: var(--color-wk-text-muted); flex-shrink: 0;" />
                <span style="display: flex; flex-direction: column; min-width: 0; flex: 1;">
                    <span style="font-size: 0.875rem; font-weight: 500;">typography-spec.fig</span>
                    <span style="font-size: 0.75rem; color: var(--color-wk-text-muted);">186 KB · Figma</span>
                </span>
            </a>
        </x-wirekit::stack>
    </x-wirekit::message>
</div>
:::

:::source{language="blade"}
<x-wirekit::message :author="['name' => 'Marcus']" timestamp="2026-04-22 09:14:00">
    Drafted the brand guidelines and a first pass at the typography spec — let me know what you think before EOD.
    <x-wirekit::stack gap="xs" style="margin-top: 0.5rem;">
        <a href="#" style="display: flex; align-items: center; gap: 0.625rem; padding: 0.5rem 0.75rem; border: 1px solid var(--color-wk-border); border-radius: var(--radius-wk-md); background: var(--color-wk-bg-elevated); text-decoration: none; color: var(--color-wk-text);">
            <x-wirekit::icon name="download" style="width: 1.25rem; height: 1.25rem; color: var(--color-wk-text-muted); flex-shrink: 0;" />
            <span style="display: flex; flex-direction: column; min-width: 0; flex: 1;">
                <span style="font-size: 0.875rem; font-weight: 500;">brand-guidelines-v3.pdf</span>
                <span style="font-size: 0.75rem; color: var(--color-wk-text-muted);">2.4 MB · PDF</span>
            </span>
        </a>
        <a href="#" style="display: flex; align-items: center; gap: 0.625rem; padding: 0.5rem 0.75rem; border: 1px solid var(--color-wk-border); border-radius: var(--radius-wk-md); background: var(--color-wk-bg-elevated); text-decoration: none; color: var(--color-wk-text);">
            <x-wirekit::icon name="download" style="width: 1.25rem; height: 1.25rem; color: var(--color-wk-text-muted); flex-shrink: 0;" />
            <span style="display: flex; flex-direction: column; min-width: 0; flex: 1;">
                <span style="font-size: 0.875rem; font-weight: 500;">typography-spec.fig</span>
                <span style="font-size: 0.75rem; color: var(--color-wk-text-muted);">186 KB · Figma</span>
            </span>
        </a>
    </x-wirekit::stack>
</x-wirekit::message>
:::

For developer apps wiring this to real Eloquent models, swap the inline `<a>` blocks for a `@foreach($message->attachments as $attachment)` loop and bind `href="{{ $attachment->url }}"` plus `{{ $attachment->name }}` / `{{ $attachment->human_size }}` accessors on the model.

## With Image in Chat

For chat-style image previews, drop a placeholder `<img>` straight into the message body at a constrained max-width. The bubble auto-sizes around the image, and the rounded corners on the image match the message bubble's radius for a polished look:

:::preview{title="Message with inline image preview"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::message :author="['name' => 'Sara']" timestamp="2026-04-22 14:08:00">
        First draft of the dashboard hero — colors pulled from the new brand palette. Thoughts?
        <img src="/placeholder/640x360?bg=5b21b6&fg=f5f3ff&label=Dashboard+Hero+Draft&gradient=none" alt="Dashboard hero draft showing dark background with accent-purple stat tiles" loading="lazy" style="display: block; max-width: 100%; width: 22rem; height: auto; margin-top: 0.5rem; border-radius: var(--radius-wk-md); border: 1px solid var(--color-wk-border);" />
    </x-wirekit::message>
</div>
:::

:::source{language="blade"}
<x-wirekit::message :author="['name' => 'Sara']" timestamp="2026-04-22 14:08:00">
    First draft of the dashboard hero — colors pulled from the new brand palette. Thoughts?
    <img src="/placeholder/640x360?bg=5b21b6&fg=f5f3ff&label=Dashboard+Hero+Draft&gradient=none" alt="Dashboard hero draft showing dark background with accent-purple stat tiles" loading="lazy" style="display: block; max-width: 100%; width: 22rem; height: auto; margin-top: 0.5rem; border-radius: var(--radius-wk-md); border: 1px solid var(--color-wk-border);" />
</x-wirekit::message>
:::

::: tip
Use `loading="lazy"` on inline chat images so a long thread doesn't trigger network fetches for messages that haven't scrolled into view. Pin the rendered `width` (or `max-width`) inline so the image doesn't blow past the message column when the source asset is wider than the chat layout — the `height: auto` keeps the natural aspect ratio. For real apps, replace the `/placeholder/...` URL with `Storage::url($message->attachment->path)` or a signed CDN URL.
:::

## Delivery status

`status` renders the delivery ladder for a message you sent. Every rung carries
its **own wording** — the glyph and color are support, never the only signal.
The glyph follows the convention every messaging app shares: **one check** =
sent to the server, **two checks** = delivered to the recipient, **two accent
checks** = read. `failed` shows its text on screen because it needs the user to
act; the rest announce to assistive technology.

:::preview{title="The delivery ladder"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::stack gap="md">
        <x-wirekit::message :author="['name' => 'You']" side="right" status="sending">Sending this one…</x-wirekit::message>
        <x-wirekit::message :author="['name' => 'You']" side="right" status="sent">Sent to the server.</x-wirekit::message>
        <x-wirekit::message :author="['name' => 'You']" side="right" status="delivered">Delivered to their device.</x-wirekit::message>
        <x-wirekit::message :author="['name' => 'You']" side="right" status="read">They have read this.</x-wirekit::message>
        <x-wirekit::message :author="['name' => 'You']" side="right" status="failed">This one did not make it.</x-wirekit::message>
    </x-wirekit::stack>
</div>
:::

:::source{language="blade"}
<x-wirekit::stack gap="md">
    <x-wirekit::message :author="['name' => 'You']" side="right" status="sending">Sending this one…</x-wirekit::message>
    <x-wirekit::message :author="['name' => 'You']" side="right" status="sent">Sent to the server.</x-wirekit::message>
    <x-wirekit::message :author="['name' => 'You']" side="right" status="delivered">Delivered to their device.</x-wirekit::message>
    <x-wirekit::message :author="['name' => 'You']" side="right" status="read">They have read this.</x-wirekit::message>
    <x-wirekit::message :author="['name' => 'You']" side="right" status="failed">This one did not make it.</x-wirekit::message>
</x-wirekit::stack>
:::

```blade
{{-- 1. Bind the rung to whatever your model already tracks --}}
<x-wirekit::message :author="$me" side="right" :status="$message->delivery_status">
    {{ $message->body }}
</x-wirekit::message>
```

Pair `failed` with a retry control in the `actions` slot.

### Delivery time

Pass `statusTime` and the status glyph carries a tooltip with the exact moment —
"Read at 9:15 AM". The time is also woven into the glyph's accessible text, so a
screen reader hears it without the (mouse-only) hover. Hover the checks below:

:::preview{title="Status time on hover"}
<div style="width: 24rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::stack gap="md">
        <x-wirekit::message :author="['name' => 'You']" side="right" status="delivered" status-time="2026-07-17 09:14">Delivered — hover the checks.</x-wirekit::message>
        <x-wirekit::message :author="['name' => 'You']" side="right" status="read" status-time="2026-07-17 09:15">Read — hover the checks.</x-wirekit::message>
    </x-wirekit::stack>
</div>
:::

:::source{language="blade"}
<x-wirekit::stack gap="md">
    <x-wirekit::message :author="['name' => 'You']" side="right" status="delivered" status-time="2026-07-17 09:14">Delivered — hover the checks.</x-wirekit::message>
    <x-wirekit::message :author="['name' => 'You']" side="right" status="read" status-time="2026-07-17 09:15">Read — hover the checks.</x-wirekit::message>
</x-wirekit::stack>
:::

## Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `author` | `array` | `[]` | Author data: `name`, `avatar` (URL), `role` |
| `timestamp` | `Carbon\|string` | `null` | Message timestamp |
| `timeFormat` | `string\|null` | `null` | Carbon format string for the timestamp. Default renders the locale-aware short time (`9:15 PM` for English, `21:15` for German and other 24-hour locales). Pass e.g. `H:i` to force a format. |
| `side` | `string` | `'left'` | Alignment: `left` (incoming) or `right` (outgoing) |
| `status` | `string\|null` | `null` | Delivery status for a sent message: `'sending'`, `'sent'`, `'delivered'`, `'read'`, `'failed'`. Each rung carries its own text — never conveyed by color alone. |
| `statusTime` | `Carbon\|string\|null` | `null` | When the status was reached — adds a tooltip and an accessible "Read at 9:15 AM" text on the glyph. |
| `intent` | `string` | `'neutral'` | Bubble color intent: `primary`, `neutral`, `info`, `success`, `warning`, `danger`. `primary` and `info` render identically (both tint with `--color-wk-accent`). See [Variants & Intents](../variants-and-intents.md) for the cross-component reference. |
| `actionsReveal` | `string` | `'hover'` | How the `actions` slot is revealed: `hover` (visible on hover or keyboard focus) or `always` (always visible — the accessible choice for touch devices, which have no hover). |
| `edited` | `bool` | `false` | Show "(edited)" indicator |

## Slots

| Slot | Description |
|------|-------------|
| default | Message body content |
| `reactions` | Reaction buttons below the bubble |
| `attachments` | File or media attachments |
| `actions` | Hover-revealed action menu |

## Accessibility

- `<article role="article">` root element
- `aria-labelledby` links to the message header (author + timestamp)
- Semantic `<time>` element with ISO `datetime` attribute
- Bubble width capped at `42ch` for readability

## Keyboard Interaction

This component is purely presentational and does not respond to keyboard input.

## Pitfalls

- **Don't use message in non-chat contexts.** Reach for `<x-wirekit::alert>` (status) or `<x-wirekit::callout>` (advice). The message component carries chat-specific semantics (author + timestamp).

## Design Tokens

| Token | Used for |
| --- | --- |
| `--font-wk-sans` | Bubble font family |
| `--font-wk-heading-weight` | Author name weight |
| `--text-wk-sm` / `--text-wk-md` | Header / body font size |
| `--color-wk-text` | Message body text |
| `--color-wk-text-muted` | Timestamp + edited indicator |
| `--color-wk-accent` | Right-side bubble tint + `info` / `primary` intent tint |
| `--color-wk-success` | `success` intent tint |
| `--color-wk-warning` | `warning` intent tint |
| `--color-wk-danger` | `danger` intent tint |
| `--color-wk-bg-elevated` | Bubble background base |
| `--color-wk-border` / `--color-wk-border-subtle` | Bubble border (intent-tinted for non-neutral, subtle for chat bubbles) |
| `--border-wk-width` | Bubble border width |
| `--radius-wk-lg` | Bubble border radius |
| `--transition-wk-duration` | Hover-action fade |

## Usage & Conventions

> **Prop conventions** — this component uses one or more of the shared semantic prop names (`intent` / `variant` / `tone` / `surface`). See [Prop naming conventions](/extending/prop-naming-conventions) for the canonical vocabulary, alias matrix, and decision tree.
