# MessageForwardedBundle

> A card under a message that holds one or more messages forwarded from another conversation, with their authors, times and attachments, folded when it runs long.

Source: https://raft-design.botiverse.build/components/message-forwarded-bundle. Generated from the current release; where it differs from what you remember, this page is right.

A card with the messages someone forwarded: a Forwarded header with the count and where they came from, then each message with its author, time, text and attachments. It is built on [MessageEmbed](https://raft-design.botiverse.build/components/message-embed.md) and sits in `MessageItemEmbedded`, under the body of the message that forwarded it ([MessageItem](https://raft-design.botiverse.build/components/message-item.md)). Long bundles fold on their own.

```tsx
import { useCallback, useState } from "react";
import {
  MESSAGE_FIXTURE_AVATAR_SRC,
  MESSAGE_EXAMPLE_HREFS,
} from "#/modules/message-item/message-item.fixtures.ts";
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";
import { Markdown } from "#/lib/markdown/markdown.tsx";
import { ChevronDown, ChevronUp, FileText, Forward } from "lucide-react";

import {
  MessageForwardedBundle,
  MessageForwardedBundleAttachment,
  MessageForwardedBundleAttachmentMeta,
  MessageForwardedBundleAttachmentName,
  MessageForwardedBundleAttachments,
  MessageForwardedBundleFooter,
  MessageForwardedBundleGallery,
  MessageForwardedBundleGalleryTile,
  MessageForwardedBundleHeader,
  MessageForwardedBundleHeaderContent,
  MessageForwardedBundleHeaderCount,
  MessageForwardedBundleHeaderLabel,
  MessageForwardedBundleItem,
  MessageForwardedBundleItemAuthor,
  MessageForwardedBundleItemContent,
  MessageForwardedBundleItemMeta,
  MessageForwardedBundleItems,
  MessageForwardedBundleSource,
  MessageForwardedBundleFooterAction,
} from "@botiverse/raft-ui";

export type ForwardedBundleCompositionItem = {
  id: string;
  author: string;
  createdAt: string;
  createdAtDateTime?: string;
  content: string;
  attachment?: { name: string; meta: string };
  images?: readonly string[];
};

const FORWARDED_FIXTURE_IMAGES = [
  "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='96'%20height='96'%3E%3Crect%20width='96'%20height='96'%20fill='%23dbeafe'/%3E%3Ccircle%20cx='48'%20cy='46'%20r='20'%20fill='%2360a5fa'/%3E%3C/svg%3E",
  "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='96'%20height='96'%3E%3Crect%20width='96'%20height='96'%20fill='%23dcfce7'/%3E%3Crect%20x='26'%20y='26'%20width='44'%20height='44'%20fill='%234ade80'/%3E%3C/svg%3E",
  "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='96'%20height='96'%3E%3Crect%20width='96'%20height='96'%20fill='%23fef3c7'/%3E%3Cpath%20d='M48%2028l20%2036H28z'%20fill='%23f59e0b'/%3E%3C/svg%3E",
  "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='96'%20height='96'%3E%3Crect%20width='96'%20height='96'%20fill='%23fce7f3'/%3E%3Ccircle%20cx='36'%20cy='40'%20r='14'%20fill='%23ec4899'/%3E%3Ccircle%20cx='62'%20cy='58'%20r='14'%20fill='%23f9a8d4'/%3E%3C/svg%3E",
];

export type ForwardedBundleEmbeddedCompositionProps = {
  items?: readonly ForwardedBundleCompositionItem[];
  sourceLabel?: string;
  sourceHref?: string | null;
  sourceAriaLabel?: string;
  sourceTitle?: string;
  collapsible?: boolean;
};

const DEFAULT_FORWARDED_ITEMS: readonly ForwardedBundleCompositionItem[] = [
  {
    id: "rollout",
    author: "Ada",
    createdAt: "Jul 15, 2:05 PM",
    createdAtDateTime: "2026-07-15T06:05:00.000Z",
    content: "Rollout is clear for the message reference migration.",
  },
  {
    id: "attachment-context",
    author: "Will",
    createdAt: "Jul 15, 2:09 PM",
    createdAtDateTime: "2026-07-15T06:09:00.000Z",
    content: "Keep the source case with its attachment context.",
    attachment: { name: "release-notes.md", meta: "markdown" },
  },
  {
    id: "review-context",
    author: "Mia",
    createdAt: "Jul 15, 2:12 PM",
    createdAtDateTime: "2026-07-15T06:12:00.000Z",
    content: "Keep the original context available to reviewers.",
    images: FORWARDED_FIXTURE_IMAGES,
  },
  {
    id: "thread-contract",
    author: "Nia",
    createdAt: "Jul 15, 2:16 PM",
    createdAtDateTime: "2026-07-15T06:16:00.000Z",
    content: "Follow the same message state contract in the thread.",
  },
];

function ForwardedBundleItemsContent({
  items,
}: {
  items: readonly ForwardedBundleCompositionItem[];
}) {
  return (
    <>
      {items.map((item) => (
        <MessageForwardedBundleItem key={item.id} data-testid="forwarded-bundle-item">
          <MessageForwardedBundleItemMeta>
            <MessageForwardedBundleItemAuthor>@{item.author}</MessageForwardedBundleItemAuthor>
            <span aria-hidden>·</span>
            <time dateTime={item.createdAtDateTime}>{item.createdAt}</time>
          </MessageForwardedBundleItemMeta>
          <MessageForwardedBundleItemContent>
            <Markdown>{item.content}</Markdown>
          </MessageForwardedBundleItemContent>
          {item.images?.length || item.attachment ? (
            <MessageForwardedBundleAttachments>
              {item.images?.length ? (
                <MessageForwardedBundleGallery>
                  {item.images.map((src, index) => (
                    <MessageForwardedBundleGalleryTile key={`${item.id}-image-${index}`}>
                      <img src={src} alt="" />
                    </MessageForwardedBundleGalleryTile>
                  ))}
                </MessageForwardedBundleGallery>
              ) : null}
              {item.attachment ? (
                <MessageForwardedBundleAttachment>
                  <FileText size={12} />
                  <MessageForwardedBundleAttachmentName>
                    {item.attachment.name}
                  </MessageForwardedBundleAttachmentName>
                  <MessageForwardedBundleAttachmentMeta>
                    · {item.attachment.meta}
                  </MessageForwardedBundleAttachmentMeta>
                </MessageForwardedBundleAttachment>
              ) : null}
            </MessageForwardedBundleAttachments>
          ) : null}
        </MessageForwardedBundleItem>
      ))}
    </>
  );
}

export function ForwardedBundleEmbeddedComposition({
  items = DEFAULT_FORWARDED_ITEMS,
  sourceLabel = "release-notes",
  sourceHref = MESSAGE_EXAMPLE_HREFS.releaseNotesThread,
  sourceAriaLabel = "Open source thread in release-notes",
  sourceTitle = "Open source message",
  collapsible = true,
}: ForwardedBundleEmbeddedCompositionProps = {}) {
  const [expanded, setExpanded] = useState(false);

  const handleToggleExpanded = useCallback(() => {
    setExpanded((current) => !current);
  }, []);

  return (
    <MessageItemEmbedded>
      <MessageForwardedBundle data-testid="forwarded-bundle-card">
        <MessageForwardedBundleHeader>
          <MessageForwardedBundleHeaderContent>
            <MessageForwardedBundleHeaderLabel>
              <Forward size={12} />
              Forwarded
            </MessageForwardedBundleHeaderLabel>
            <MessageForwardedBundleHeaderCount>
              {items.length} messages
            </MessageForwardedBundleHeaderCount>
          </MessageForwardedBundleHeaderContent>
          <MessageForwardedBundleSource
            data-testid="forwarded-bundle-source-label"
            title={sourceHref ? sourceTitle : undefined}
            render={sourceHref ? <a href={sourceHref} aria-label={sourceAriaLabel} /> : undefined}
          >
            from #{sourceLabel}
          </MessageForwardedBundleSource>
        </MessageForwardedBundleHeader>
        <MessageForwardedBundleItems collapsed={collapsible && !expanded}>
          <ForwardedBundleItemsContent items={items} />
        </MessageForwardedBundleItems>
        {collapsible ? (
          <MessageForwardedBundleFooter>
            <MessageForwardedBundleFooterAction onClick={handleToggleExpanded}>
              {expanded ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
              {expanded ? "Collapse" : `Show all ${items.length} messages`}
            </MessageForwardedBundleFooterAction>
          </MessageForwardedBundleFooter>
        ) : null}
      </MessageForwardedBundle>
    </MessageItemEmbedded>
  );
}
```

## When to use

| Situation                                                | Use                                                                                |
| -------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| One or more messages passed on from another conversation | MessageForwardedBundle                                                             |
| A link to one message, shown as an excerpt               | [MessageQuotedPreview](https://raft-design.botiverse.build/components/message-quoted-preview.md)        |
| A link to a message or a channel, inline in the text     | A reference chip ([MessageItem](https://raft-design.botiverse.build/components/message-item.md#in-the-body))      |
| A quote typed into the message                           | A Markdown blockquote ([MessageItem](https://raft-design.botiverse.build/components/message-item.md#in-the-body)) |

## Anatomy

- `MessageForwardedBundle`: the card. Required.
- `MessageForwardedBundleHeader`: the top row; it wraps on a narrow column. Required.
  - `MessageForwardedBundleHeaderContent`, with `MessageForwardedBundleHeaderLabel` (an icon and Forwarded) and `MessageForwardedBundleHeaderCount` (in Space Mono, `foreground-hint`).
  - The source, at the end of the row: `MessageForwardedBundleSource` when it can be opened, `MessageForwardedBundleSourceLabel` when it can't. Optional.
- `MessageForwardedBundleItems`: the list of messages, and the part that folds. Required.
- `MessageForwardedBundleItem`: one forwarded message. At least 1.
  - `MessageForwardedBundleItemMeta`: `MessageForwardedBundleItemAuthor`, then the time in Space Mono.
  - `MessageForwardedBundleItemContent`: the text. Required: the fold measures only this part.
  - `MessageForwardedBundleAttachments`: `MessageForwardedBundleAttachment` chips (`MessageForwardedBundleAttachmentName`, `MessageForwardedBundleAttachmentMeta`), and `MessageForwardedBundleGallery` with a `MessageForwardedBundleGalleryTile` per image. Optional.
- `MessageForwardedBundleFooter` with `MessageForwardedBundleFooterAction`: the action that unfolds the list. Optional.

## Variants

| Variant                                             | Use for                                                                                             |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| A few short messages                                | The common case: shown in full, with no footer                                                      |
| Long content                                        | Text beyond the fold limit: the list folds and the footer appears                                   |
| Source that opens (`MessageForwardedBundleSource`)  | A source the person can open, such as a public channel or a thread in one                           |
| Static source (`MessageForwardedBundleSourceLabel`) | A source that can't be opened, such as a preview in the composer or a message that no longer exists |
| `fullWidth`                                         | A place where the card fills the column instead of keeping its maximum width                        |

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@botiverse/raft-ui";
import {
  MessageForwardedBundle,
  MessageForwardedBundleHeader,
  MessageForwardedBundleHeaderContent,
  MessageForwardedBundleHeaderCount,
  MessageForwardedBundleHeaderLabel,
  MessageForwardedBundleItem,
  MessageForwardedBundleItemAuthor,
  MessageForwardedBundleItemContent,
  MessageForwardedBundleItemMeta,
  MessageForwardedBundleItems,
} from "@botiverse/raft-ui";

import { Markdown } from "#/lib/markdown/markdown.tsx";
import { MESSAGE_FIXTURE_AVATAR_SRC } from "#/modules/message-item/message-item.fixtures.ts";
import { Forward } from "lucide-react";
import {
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

export function ForwardedBundleCompactComposition() {
  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="agent">
          <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
          <AvatarFallback>AT</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Atlas</MessageItemSender>
          <MessageItemMeta>owner</MessageItemMeta>
          <MessageItemTime>2:25 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>Forwarded 2 messages</MessageItemBody>
        <MessageItemEmbedded>
          <MessageForwardedBundle>
            <MessageForwardedBundleHeader>
              <MessageForwardedBundleHeaderContent>
                <MessageForwardedBundleHeaderLabel>
                  <Forward size={12} />
                  Forwarded
                </MessageForwardedBundleHeaderLabel>
                <MessageForwardedBundleHeaderCount>2 messages</MessageForwardedBundleHeaderCount>
              </MessageForwardedBundleHeaderContent>
            </MessageForwardedBundleHeader>
            <MessageForwardedBundleItems>
              <MessageForwardedBundleItem>
                <MessageForwardedBundleItemMeta>
                  <MessageForwardedBundleItemAuthor>@Ada</MessageForwardedBundleItemAuthor>
                  <time dateTime="2026-07-15T06:05:00.000Z">Jul 15, 2:05 PM</time>
                </MessageForwardedBundleItemMeta>
                <MessageForwardedBundleItemContent>
                  <Markdown>Rollout is clear for the message reference migration.</Markdown>
                </MessageForwardedBundleItemContent>
              </MessageForwardedBundleItem>
              <MessageForwardedBundleItem>
                <MessageForwardedBundleItemMeta>
                  <MessageForwardedBundleItemAuthor>@Will</MessageForwardedBundleItemAuthor>
                  <time dateTime="2026-07-15T06:09:00.000Z">Jul 15, 2:09 PM</time>
                </MessageForwardedBundleItemMeta>
                <MessageForwardedBundleItemContent>
                  <Markdown>Keep the source case with its attachment context.</Markdown>
                </MessageForwardedBundleItemContent>
              </MessageForwardedBundleItem>
            </MessageForwardedBundleItems>
          </MessageForwardedBundle>
        </MessageItemEmbedded>
      </MessageItemContent>
    </MessageItem>
  );
}
```

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@botiverse/raft-ui";
import {
  MessageForwardedBundle,
  MessageForwardedBundleAttachment,
  MessageForwardedBundleAttachmentMeta,
  MessageForwardedBundleAttachmentName,
  MessageForwardedBundleAttachments,
  MessageForwardedBundleFooter,
  MessageForwardedBundleHeader,
  MessageForwardedBundleHeaderContent,
  MessageForwardedBundleHeaderCount,
  MessageForwardedBundleHeaderLabel,
  MessageForwardedBundleItem,
  MessageForwardedBundleItemAuthor,
  MessageForwardedBundleItemContent,
  MessageForwardedBundleItemMeta,
  MessageForwardedBundleItems,
  MessageForwardedBundleFooterAction,
} from "@botiverse/raft-ui";

import { Markdown } from "#/lib/markdown/markdown.tsx";
import { MESSAGE_FIXTURE_AVATAR_SRC } from "#/modules/message-item/message-item.fixtures.ts";
import { ChevronDown, ChevronUp, FileText, Forward } from "lucide-react";
import {
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

function LongForwardedItem() {
  return (
    <MessageForwardedBundleItem>
      <MessageForwardedBundleItemMeta>
        <MessageForwardedBundleItemAuthor>@Nia</MessageForwardedBundleItemAuthor>
        <time dateTime="2026-07-15T06:16:00.000Z">Jul 15, 2:16 PM</time>
      </MessageForwardedBundleItemMeta>
      <MessageForwardedBundleItemContent>
        <Markdown>{`Before we cut over, keep the read model running against both event streams and validate account, workspace, and channel permissions in staging.

The migration needs an explicit rollback checkpoint after the first production cohort, plus a documented handoff for reconciliation and replay.

Review the operational checklist with the owners of notifications, search, and thread state before scheduling the final rollout window.

Also decide the dual-write window up front: how long both writers stay live, who watches the divergence dashboard, and which metrics gate the next cohort.

Finally, write down what "done" means for the old path — read-only, disconnected, or deleted — and add a dated note to the runbook so the next person on call can find the decision.`}</Markdown>
      </MessageForwardedBundleItemContent>
      <MessageForwardedBundleAttachments>
        <MessageForwardedBundleAttachment>
          <FileText size={12} />
          <MessageForwardedBundleAttachmentName>
            migration-checklist.md
          </MessageForwardedBundleAttachmentName>
          <MessageForwardedBundleAttachmentMeta>· markdown</MessageForwardedBundleAttachmentMeta>
        </MessageForwardedBundleAttachment>
        <MessageForwardedBundleAttachment>
          <FileText size={12} />
          <MessageForwardedBundleAttachmentName>
            webhook-cutover-plan.pdf
          </MessageForwardedBundleAttachmentName>
          <MessageForwardedBundleAttachmentMeta>· pdf</MessageForwardedBundleAttachmentMeta>
        </MessageForwardedBundleAttachment>
      </MessageForwardedBundleAttachments>
    </MessageForwardedBundleItem>
  );
}

export function ForwardedBundleLongContentComposition() {
  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="agent">
          <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
          <AvatarFallback>AT</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Atlas</MessageItemSender>
          <MessageItemMeta>owner</MessageItemMeta>
          <MessageItemTime>2:27 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>Forwarded 1 message</MessageItemBody>
        <MessageItemEmbedded>
          <MessageForwardedBundle>
            <MessageForwardedBundleHeader>
              <MessageForwardedBundleHeaderContent>
                <MessageForwardedBundleHeaderLabel>
                  <Forward size={12} />
                  Forwarded
                </MessageForwardedBundleHeaderLabel>
                <MessageForwardedBundleHeaderCount>message</MessageForwardedBundleHeaderCount>
              </MessageForwardedBundleHeaderContent>
            </MessageForwardedBundleHeader>
            <MessageForwardedBundleItems>
              <LongForwardedItem />
            </MessageForwardedBundleItems>
            <MessageForwardedBundleFooter>
              <MessageForwardedBundleFooterAction
                expandedContent={
                  <>
                    <ChevronUp size={12} />
                    Collapse
                  </>
                }
              >
                <ChevronDown size={12} />
                Show all 1 message
              </MessageForwardedBundleFooterAction>
            </MessageForwardedBundleFooter>
          </MessageForwardedBundle>
        </MessageItemEmbedded>
      </MessageItemContent>
    </MessageItem>
  );
}
```

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@botiverse/raft-ui";
import {
  MessageForwardedBundle,
  MessageForwardedBundleHeader,
  MessageForwardedBundleHeaderContent,
  MessageForwardedBundleHeaderCount,
  MessageForwardedBundleHeaderLabel,
  MessageForwardedBundleItem,
  MessageForwardedBundleItemAuthor,
  MessageForwardedBundleItemContent,
  MessageForwardedBundleItemMeta,
  MessageForwardedBundleItems,
  MessageForwardedBundleSource,
} from "@botiverse/raft-ui";

import { Markdown } from "#/lib/markdown/markdown.tsx";
import {
  MESSAGE_FIXTURE_AVATAR_SRC,
  MESSAGE_EXAMPLE_HREFS,
} from "#/modules/message-item/message-item.fixtures.ts";
import { Forward } from "lucide-react";
import {
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

export function ForwardedBundleThreadComposition() {
  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="agent">
          <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
          <AvatarFallback>AT</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Atlas</MessageItemSender>
          <MessageItemMeta>owner</MessageItemMeta>
          <MessageItemTime>2:26 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>Forwarded 1 message</MessageItemBody>
        <MessageItemEmbedded>
          <MessageForwardedBundle>
            <MessageForwardedBundleHeader>
              <MessageForwardedBundleHeaderContent>
                <MessageForwardedBundleHeaderLabel>
                  <Forward size={12} />
                  Forwarded
                </MessageForwardedBundleHeaderLabel>
                <MessageForwardedBundleHeaderCount>message</MessageForwardedBundleHeaderCount>
              </MessageForwardedBundleHeaderContent>
              <MessageForwardedBundleSource
                render={
                  <a
                    href={MESSAGE_EXAMPLE_HREFS.releaseNotesThread}
                    aria-label="Open source thread in release-notes"
                  />
                }
              >
                from #release-notes · thread
              </MessageForwardedBundleSource>
            </MessageForwardedBundleHeader>
            <MessageForwardedBundleItems>
              <MessageForwardedBundleItem>
                <MessageForwardedBundleItemMeta>
                  <MessageForwardedBundleItemAuthor>@Mia</MessageForwardedBundleItemAuthor>
                  <time dateTime="2026-07-15T06:12:00.000Z">Jul 15, 2:12 PM</time>
                </MessageForwardedBundleItemMeta>
                <MessageForwardedBundleItemContent>
                  <Markdown>Follow the same message state contract in the thread.</Markdown>
                </MessageForwardedBundleItemContent>
              </MessageForwardedBundleItem>
            </MessageForwardedBundleItems>
          </MessageForwardedBundle>
        </MessageItemEmbedded>
      </MessageItemContent>
    </MessageItem>
  );
}
```

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@botiverse/raft-ui";
import {
  MessageForwardedBundle,
  MessageForwardedBundleHeader,
  MessageForwardedBundleHeaderContent,
  MessageForwardedBundleHeaderCount,
  MessageForwardedBundleHeaderLabel,
  MessageForwardedBundleItem,
  MessageForwardedBundleItemAuthor,
  MessageForwardedBundleItemContent,
  MessageForwardedBundleItemMeta,
  MessageForwardedBundleItems,
  MessageForwardedBundleSourceLabel,
} from "@botiverse/raft-ui";

import { Markdown } from "#/lib/markdown/markdown.tsx";
import { MESSAGE_FIXTURE_AVATAR_SRC } from "#/modules/message-item/message-item.fixtures.ts";
import { Forward } from "lucide-react";
import {
  MessageItem,
  MessageItemAvatarSlot,
  MessageItemBody,
  MessageItemContent,
  MessageItemEmbedded,
  MessageItemHeader,
  MessageItemMeta,
  MessageItemSender,
  MessageItemTime,
} from "@botiverse/raft-ui";

export function ForwardedBundleStaticSourceComposition() {
  return (
    <MessageItem>
      <MessageItemAvatarSlot>
        <Avatar size="md" type="agent">
          <AvatarImage src={MESSAGE_FIXTURE_AVATAR_SRC} alt="" />
          <AvatarFallback>AT</AvatarFallback>
        </Avatar>
      </MessageItemAvatarSlot>
      <MessageItemContent>
        <MessageItemHeader>
          <MessageItemSender>Atlas</MessageItemSender>
          <MessageItemMeta>owner</MessageItemMeta>
          <MessageItemTime>2:28 PM</MessageItemTime>
        </MessageItemHeader>
        <MessageItemBody>Forwarded 1 message</MessageItemBody>
        <MessageItemEmbedded>
          <MessageForwardedBundle>
            <MessageForwardedBundleHeader>
              <MessageForwardedBundleHeaderContent>
                <MessageForwardedBundleHeaderLabel>
                  <Forward size={12} />
                  Forwarded
                </MessageForwardedBundleHeaderLabel>
                <MessageForwardedBundleHeaderCount>message</MessageForwardedBundleHeaderCount>
              </MessageForwardedBundleHeaderContent>
              <MessageForwardedBundleSourceLabel>
                from #release-notes
              </MessageForwardedBundleSourceLabel>
            </MessageForwardedBundleHeader>
            <MessageForwardedBundleItems>
              <MessageForwardedBundleItem>
                <MessageForwardedBundleItemMeta>
                  <MessageForwardedBundleItemAuthor>@Kai</MessageForwardedBundleItemAuthor>
                  <time dateTime="2026-07-15T06:19:00.000Z">Jul 15, 2:19 PM</time>
                </MessageForwardedBundleItemMeta>
                <MessageForwardedBundleItemContent>
                  <Markdown>The original message is no longer available to open.</Markdown>
                </MessageForwardedBundleItemContent>
              </MessageForwardedBundleItem>
            </MessageForwardedBundleItems>
          </MessageForwardedBundle>
        </MessageItemEmbedded>
      </MessageItemContent>
    </MessageItem>
  );
}
```

## Behavior

- The card is at most 544px wide, and narrower when the column is.
- By default the bundle folds itself: when the text of all items together runs past `foldLineLimit` lines (8), the list is cut at that height and fades out at the bottom into `layer-card`.
- The footer shows only while the list is folded. Its action unfolds the list and folds it again; `expandedContent` is its label while unfolded.
- The bundle measures again when content arrives or the width changes. Once the person has folded or unfolded it, it stays as they left it, unless the content no longer needs folding.
- Pass `collapsed` to `MessageForwardedBundleItems` to control the fold yourself, as a detail view that shows everything does. The footer then always renders, and you label it.
- To make the footer action open a detail view instead of unfolding, call `preventDefault` in its `onClick`.
- `MessageForwardedBundleGallery` lays images in rows of 3, and 4 images as 2 rows of 2, at the sizes of a message's own gallery ([MessageAttachment](https://raft-design.botiverse.build/components/message-attachment.md)).

## Content

- Label the header Forwarded, and give the count in Space Mono: "3 messages".
- Write the source as "from #launch-week", with "· thread" when it is a thread.
- Show a source only when the person may see it; leave it out for a direct message, a private channel or a missing source.
- Name each author by their display name, not a handle ([Writing](https://raft-design.botiverse.build/content/writing.md#voice)).
- Label the footer action with what it does, in Title Case: Show All 6 Messages, then Collapse.
- Give each attachment chip its file name and type: "release-notes.md · markdown".
- An attachment chip takes 5.5px before its icon and 6.5px after its text, 12px in all as before: a file icon is drawn 1.75px inside its box, so its ink and the last letter both sit about 7.2px from the edges ([Alignment](https://raft-design.botiverse.build/foundations/alignment.md#chips)).

## Accessibility

- The parts are plain elements built with Base UI `useRender`; each `MessageForwardedBundleItem` is an `<article>`.
- `MessageForwardedBundleSource` is a `<button>`. When it navigates, render it as an `<a>` with an `aria-label` that names the place: "Open source thread in #launch-week".
- While the bundle owns the fold, `MessageForwardedBundleFooterAction` sets `aria-expanded`. With `collapsed`, set it yourself.
- Put each item's time in a `<time>` element with its `dateTime`.
- `interactive` on `MessageForwardedBundleAttachment` only adds the hover and focus look. Render the chip as a `<button>` or `<a>` that opens the file, with an `aria-label` such as "Open release-notes.md".
- Give gallery images an `alt` as in [MessageAttachment](https://raft-design.botiverse.build/components/message-attachment.md#accessibility).

## Rules

- Wrap each item's text in `MessageForwardedBundleItemContent`: text outside it isn't measured, and the bundle won't fold.
- Don't render a bundle with no items.

## API

### MessageForwardedBundle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `fullWidth` | `boolean` | `false` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleAttachment

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `interactive` | `boolean` | `false` | Marks the attachment as actionable (the consumer wires the open action on `render`). Adds the pointer/hover/focus affordance to the chip. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleAttachmentMeta

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleAttachmentName

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleAttachments

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleFooter

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleGallery

Image rows for forwarded attachments. The gallery owns the layout — rows of three (a lone row of exactly four splits two-and-two) at the message gallery row heights — and composes the shared MessageImageGallery surfaces, so forwarded images are the same component family as images in a message. Consumers only place the media (`MessageForwardedBundleGalleryTile`).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleGalleryTile

A media cell in the forwarded gallery rows: the shared message gallery item (its surface and rounding come from MessageImageGalleryItem).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleHeader

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleHeaderContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleHeaderCount

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleHeaderLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleItemAuthor

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleItemContent

The item's text body. The component-owned fold measures these slots: when their combined content renders beyond the fold line limit the card folds.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleItemMeta

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleItems

The items surface. Without a `collapsed` prop the fold is component-owned: the surface measures its item-content slots and collapses itself to `foldLineLimit` lines when the text renders beyond that many lines in total (eight by default). Passing `collapsed` switches to the controlled mode (the detail view pins everything open) and skips the measurement.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `collapsed` | `boolean` |  |  |
| `foldLineLimit` | `number` | `FOLD_LINE_LIMIT` | How many rendered text lines the default fold allows before folding. Only used in the component-owned mode (`collapsed` omitted). |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleSource

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleSourceLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### MessageForwardedBundleFooterAction

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `expandedContent` | `ReactNode` |  | What the action shows while the component-owned fold is expanded (`children` render while it is folded). Ignored in the controlled mode, where the consumer drives and labels the fold itself. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

## Changes

- 0.3.0 (Breaking Changes): Renamed `MessageForwardedBundleToggle` to `MessageForwardedBundleFooterAction`, including its props type and `data-slot`.
