---
title: Message Group
description: A run of consecutive messages from one sender, with the avatar and the name printed once instead of on every bubble.
visibility: guest
draft: false
---

# Message Group

People send three messages in a row. Rendering the avatar and the name above each
of them turns a conversation into a column of identical headers — the thread stops
reading as a conversation and starts reading as a table. This wraps the run: the
sender appears once, the bubbles sit closer together, and their inner corners
round tighter so the three read as one turn.

Each message keeps its own timestamp. A run shares a sender, not a moment.

## Basic Usage

Put the sender on the group and leave it off the [messages](/components/message)
inside — they take the same `author` shape, so you can pass the same array to
either.

:::preview{title="Three messages, one sender"}
<div style="width: 26rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::stack gap="md">
        <x-wirekit::message-group :author="['name' => 'Ada']">
            <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 09:14">Found it — the build only fails on the release line.</x-wirekit::message>
            <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 09:14">The gate never runs that step on a branch.</x-wirekit::message>
            <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 09:15">Pushing a fix now.</x-wirekit::message>
        </x-wirekit::message-group>
        <x-wirekit::message :author="['name' => 'Grace']" side="right" timestamp="2026-07-17 09:16">That explains the week.</x-wirekit::message>
    </x-wirekit::stack>
</div>
:::

:::source{language="blade"}
<x-wirekit::message-group :author="['name' => 'Ada']">
    <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 09:14">Found it — the build only fails on the release line.</x-wirekit::message>
    <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 09:14">The gate never runs that step on a branch.</x-wirekit::message>
    <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 09:15">Pushing a fix now.</x-wirekit::message>
</x-wirekit::message-group>
:::

## Sides

`side` mirrors [Message](/components/message)'s alignment, so a run from the person
reading the thread sits on the other side.

:::preview{title="A run on each side"}
<div style="width: 26rem; max-width: 100%; margin-inline: auto;">
    <x-wirekit::stack gap="md">
        <x-wirekit::message-group :author="['name' => 'Ada']">
            <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 09:14">Two minutes.</x-wirekit::message>
            <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 09:15">Maybe three.</x-wirekit::message>
        </x-wirekit::message-group>
        <x-wirekit::message-group :author="['name' => 'You']" side="right">
            <x-wirekit::message :author="['name' => 'You']" side="right" timestamp="2026-07-17 09:16">No rush.</x-wirekit::message>
            <x-wirekit::message :author="['name' => 'You']" side="right" timestamp="2026-07-17 09:16">I will read it after the meeting.</x-wirekit::message>
        </x-wirekit::message-group>
    </x-wirekit::stack>
</div>
:::

:::source{language="blade"}
<x-wirekit::message-group :author="['name' => 'Ada']">
    <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 09:14">Two minutes.</x-wirekit::message>
    <x-wirekit::message :author="['name' => 'Ada']" timestamp="2026-07-17 09:15">Maybe three.</x-wirekit::message>
</x-wirekit::message-group>

<x-wirekit::message-group :author="['name' => 'You']" side="right">
    <x-wirekit::message :author="['name' => 'You']" side="right" timestamp="2026-07-17 09:16">No rush.</x-wirekit::message>
    <x-wirekit::message :author="['name' => 'You']" side="right" timestamp="2026-07-17 09:16">I will read it after the meeting.</x-wirekit::message>
</x-wirekit::message-group>
:::

## What the group changes about the messages inside

Nothing in the markup of a message changes — a message renders the same whether it
sits in a group or not. What the group does is style its own run:

- The **avatar** of each message inside is not painted. Its accessible name is the
  sender's name, which the group already carries, so a second copy is noise.
- The **name** is hidden visually and **stays in the accessibility tree**. A screen
  reader that lands on the third bubble of a run still has to be able to say who
  wrote it.
- The **inner corners** round tighter, using logical properties, so a right-to-left
  thread mirrors without a second rule.
- The **gap** between bubbles is the small spacing token rather than the one between
  senders.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `author` | array\|string | `[]` | The sender, in the same shape [Message](/components/message) takes: `['name' => …, 'avatar' => …]`. A bare string is read as the name. It is printed once, above the run |
| `side` | string | `'left'` | `left` or `right`, mirroring Message |
| `scope` | string\|null | `null` | Scoped personalization name |

## Accessibility

- The group is a `role="group"` named after its sender — "Messages from Ada" — so a
  reader entering the run hears whose it is before the first message. Without a
  name there is no label: a group that announces nothing is worse than an unnamed
  one.
- Each message inside keeps its own `role="article"` and its own accessible name,
  so the run is navigable message by message.
- The sender's name inside each message is hidden with the visually-hidden pattern
  rather than `display: none`, which would take it out of the accessibility tree
  along with the pixels.

## Keyboard Interaction

This component is presentational and does not respond to keyboard input. The
messages inside keep whatever their own actions provide.

## Pitfalls

- **Group one sender, not one topic.** The moment the sender changes, close the
  group and open the next one — otherwise the single name above the run is wrong
  for everything below it.
- **Keep the timestamps.** They are what makes a run readable as a sequence; the
  group deliberately does not collapse them.
- **Do not nest a group in a group.** The inner one would print a second sender
  inside a run that already has one.

## Design Tokens

| Element | Token |
| --- | --- |
| Gap between sender and messages | `--space-wk-sm` |
| Gap between messages in a run | `--space-wk-xs` |
| Rounded inner corners | `--radius-wk-sm` |
| Font family | `--font-wk-sans` |

## Further Reading

- [Message](/components/message) — the bubble this groups
- [Message Typing](/components/message-typing) — the indicator that follows a run
- [WAI-ARIA: `group` role](https://www.w3.org/TR/wai-aria-1.2/#group)
