# Grouped consecutive messages

> Consecutive turns from one sender collapse into a single BubbleGroup.

A shadcn/ui block from the [@reui](https://reui.io) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/reui/c-bubble-3).

- Page, with a live preview: https://shadcn.exchange/r/reui/c-bubble-3
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/reui/c-bubble-3-light-2ad105c92379ff14.png
- Registry item: https://reui.io/r/new-york-v4/c-bubble-3.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://reui.io/r/new-york-v4/c-bubble-3.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `bubble`

## Files

### `c-bubble-3.tsx`

```tsx
import {
  Bubble,
  BubbleContent,
  BubbleGroup,
} from "@/components/ui/bubble"

// Consecutive lines from one sender are one entry with an array of lines, not
// four separate turns. Grouping is a shape in the data first, and BubbleGroup
// is only what renders that shape.
const handoff: { id: string; from: "marco" | "you"; lines: string[] }[] = [
  {
    id: "g1",
    from: "marco",
    lines: [
      "Pager has been quiet since midnight.",
      "The retry queue drained at 02:10 and stayed at zero.",
      "Runbook link is in the incident, third comment down.",
    ],
  },
  {
    id: "g2",
    from: "you",
    lines: ["Thanks. Anything still open on the payments worker?"],
  },
  {
    id: "g3",
    from: "you",
    lines: [
      "I can pick up the backoff change this morning if nobody has started it.",
      "Otherwise I will watch the dashboard until standup.",
    ],
  },
  {
    id: "g4",
    from: "marco",
    lines: ["Not started, so it is yours. Ticket PLT-812."],
  },
]

export function Pattern() {
  return (
    // The group's own gap is tighter than this thread gap around it, and that
    // difference is the entire grouping signal. Equalise the two and the reader
    // sees eight unrelated messages instead of four turns.
    <div className="mx-auto flex w-full max-w-sm flex-col gap-5">
      {handoff.map((group) => (
        // align stays on each Bubble. The group stretches to the column and has
        // no side of its own, so an align passed to it would do nothing at all.
        <BubbleGroup key={group.id}>
          {/* The group is the unit one person spoke, so the name is announced
              once here rather than in front of every line inside it. */}
          <span className="sr-only">
            {group.from === "you" ? "You said: " : "Marco said: "}
          </span>
          {group.lines.map((line) => (
            <Bubble
              key={line}
              variant={group.from === "you" ? "default" : "secondary"}
              align={group.from === "you" ? "end" : "start"}
            >
              <BubbleContent>{line}</BubbleContent>
            </Bubble>
          ))}
        </BubbleGroup>
      ))}
      <p className="text-muted-foreground self-end text-xs">Delivered 9:41</p>
    </div>
  )
}
```
