---
title: Message
description: Conversation row with avatars, metadata, and alignment.
group: Components
order: 47
---

Lay out a message with its avatar, alignment, header, and footer.

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Bubble, BubbleContent, BubbleReactions } from "@/components/ui/bubble"
import { Marker, MarkerContent } from "@/components/ui/marker"
import {
  Message,
  MessageAvatar,
  MessageContent,
  MessageFooter,
} from "@/components/ui/message"

export function Example() {
  return (
    <div className="flex w-full flex-col gap-3">
      <Message align="end">
        <MessageAvatar>
          <Avatar size="sm">
            <AvatarImage src="/assets/brand/demos/avatars/avatar-1.png" alt="Mina Cho" />
            <AvatarFallback>MC</AvatarFallback>
          </Avatar>
        </MessageAvatar>
        <MessageContent>
          <Bubble variant="default" align="end">
            <BubbleContent>Deploying to prod real quick.</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message>
        <MessageAvatar>
          <Avatar size="sm">
            <AvatarImage src="/assets/brand/demos/avatars/avatar-2.png" alt="Jordan Blake" />
            <AvatarFallback>JB</AvatarFallback>
          </Avatar>
        </MessageAvatar>
        <MessageContent>
          <Bubble variant="secondary">
            <BubbleContent>It's 4:55 PM. On a Friday.</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message align="end">
        <MessageAvatar>
          <Avatar size="sm">
            <AvatarImage src="/assets/brand/demos/avatars/avatar-1.png" alt="Mina Cho" />
            <AvatarFallback>MC</AvatarFallback>
          </Avatar>
        </MessageAvatar>
        <MessageContent>
          <Bubble variant="default" align="end">
            <BubbleContent>It's a one-line change.</BubbleContent>
          </Bubble>
          <MessageFooter>Delivered</MessageFooter>
        </MessageContent>
      </Message>
      <Message>
        <MessageAvatar>
          <Avatar size="sm">
            <AvatarImage src="/assets/brand/demos/avatars/avatar-2.png" alt="Jordan Blake" />
            <AvatarFallback>JB</AvatarFallback>
          </Avatar>
        </MessageAvatar>
        <MessageContent>
          <Bubble variant="secondary">
            <BubbleContent>It's always a one-line change.</BubbleContent>
          </Bubble>
          <Bubble variant="secondary">
            <BubbleContent>Alright, let me take a look.</BubbleContent>
            <BubbleReactions>👍</BubbleReactions>
          </Bubble>
        </MessageContent>
      </Message>
      <Marker role="status">
        <MarkerContent className="shimmer">Oliver is typing…</MarkerContent>
      </Marker>
    </div>
  )
}
```

`Message` handles the row layout. Use [Bubble](/docs/components/bubble) for the visible surface and [Avatar](/docs/components/avatar) for portraits.

## Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add message
```

## Usage

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Message, MessageAvatar, MessageContent } from "@/components/ui/message"
```

```tsx
<Message>
  <MessageAvatar>
    <Avatar size="sm">
      <AvatarImage src="/assets/brand/demos/avatars/avatar-1.png" alt="Mina Cho" />
      <AvatarFallback>MC</AvatarFallback>
    </Avatar>
  </MessageAvatar>
  <MessageContent>
    <Bubble>
      <BubbleContent>How can I help you today?</BubbleContent>
    </Bubble>
  </MessageContent>
</Message>
```

## Composition

```tree
Message
├── MessageAvatar
└── MessageContent
    ├── MessageHeader
    ├── Bubble
    └── MessageFooter
```

Use `MessageGroup` to stack consecutive messages from one sender:

```tree
MessageGroup
├── Message
└── Message
```

## Basic

A short thread with start and end alignment, footer status, reactions, and a typing [Marker](/docs/components/marker).

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Bubble, BubbleContent, BubbleReactions } from "@/components/ui/bubble"
import { Marker, MarkerContent } from "@/components/ui/marker"
import {
  Message,
  MessageAvatar,
  MessageContent,
  MessageFooter,
} from "@/components/ui/message"

export function Example() {
  return (
    <div className="flex w-full flex-col gap-3">
      <Message align="end">
        <MessageAvatar>
          <Avatar size="sm">
            <AvatarImage src="/assets/brand/demos/avatars/avatar-1.png" alt="Mina Cho" />
            <AvatarFallback>MC</AvatarFallback>
          </Avatar>
        </MessageAvatar>
        <MessageContent>
          <Bubble variant="default" align="end">
            <BubbleContent>Deploying to prod real quick.</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message>
        <MessageAvatar>
          <Avatar size="sm">
            <AvatarImage src="/assets/brand/demos/avatars/avatar-2.png" alt="Jordan Blake" />
            <AvatarFallback>JB</AvatarFallback>
          </Avatar>
        </MessageAvatar>
        <MessageContent>
          <Bubble variant="secondary">
            <BubbleContent>It's 4:55 PM. On a Friday.</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message align="end">
        <MessageAvatar>
          <Avatar size="sm">
            <AvatarImage src="/assets/brand/demos/avatars/avatar-1.png" alt="Mina Cho" />
            <AvatarFallback>MC</AvatarFallback>
          </Avatar>
        </MessageAvatar>
        <MessageContent>
          <Bubble variant="default" align="end">
            <BubbleContent>It's a one-line change.</BubbleContent>
          </Bubble>
          <MessageFooter>Delivered</MessageFooter>
        </MessageContent>
      </Message>
      <Message>
        <MessageAvatar>
          <Avatar size="sm">
            <AvatarImage src="/assets/brand/demos/avatars/avatar-2.png" alt="Jordan Blake" />
            <AvatarFallback>JB</AvatarFallback>
          </Avatar>
        </MessageAvatar>
        <MessageContent>
          <Bubble variant="secondary">
            <BubbleContent>It's always a one-line change.</BubbleContent>
          </Bubble>
          <Bubble variant="secondary">
            <BubbleContent>Alright, let me take a look.</BubbleContent>
            <BubbleReactions>👍</BubbleReactions>
          </Bubble>
        </MessageContent>
      </Message>
      <Marker role="status">
        <MarkerContent className="shimmer">Oliver is typing…</MarkerContent>
      </Marker>
    </div>
  )
}
```

## Avatar

Place `MessageAvatar` beside the message. `align="end"` flips the row. The avatar centers on the bubble’s first line of text, while empty avatar slots keep grouped rows aligned.

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import {
  Message,
  MessageAvatar,
  MessageContent,
  MessageGroup,
} from "@/components/ui/message"

export function Example() {
  return (
    <MessageGroup>
      <Message>
        <MessageAvatar />
        <MessageContent>
          <Bubble variant="secondary">
            <BubbleContent>The build failed during dependency installation.</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message>
        <MessageAvatar />
        <MessageContent>
          <Bubble variant="secondary">
            <BubbleContent>Can you share the exact error?</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message>
        <MessageAvatar>
          <Avatar size="sm">
            <AvatarImage src="/assets/brand/demos/avatars/avatar-2.png" alt="Jordan Blake" />
            <AvatarFallback>JB</AvatarFallback>
          </Avatar>
        </MessageAvatar>
        <MessageContent>
          <Bubble variant="secondary">
            <BubbleContent>Here's the error from the logs</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
    </MessageGroup>
  )
}
```

| align | Description |
| --- | --- |
| `start` | Align the message to the start of the conversation. |
| `end` | Align the message to the end of the conversation. |

## Group

Use `MessageGroup` for consecutive messages from one sender. Leave earlier `MessageAvatar` slots empty so each row lines up with the final avatar.

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import {
  Message,
  MessageAvatar,
  MessageContent,
  MessageGroup,
} from "@/components/ui/message"

export function Example() {
  return (
    <div className="flex w-full flex-col gap-3">
      <MessageGroup>
        <Message>
          <MessageAvatar />
          <MessageContent>
            <Bubble variant="secondary">
              <BubbleContent>I checked the registry addresses.</BubbleContent>
            </Bubble>
          </MessageContent>
        </Message>
        <Message>
          <MessageAvatar>
            <Avatar size="sm">
              <AvatarImage src="/assets/brand/demos/avatars/avatar-3.png" alt="Priya Nair" />
              <AvatarFallback>PN</AvatarFallback>
            </Avatar>
          </MessageAvatar>
          <MessageContent>
            <Bubble variant="secondary">
              <BubbleContent>
                The component and example JSON now live under the UI registry.
              </BubbleContent>
            </Bubble>
          </MessageContent>
        </Message>
      </MessageGroup>
    </div>
  )
}
```

## Header and Footer

Use `MessageHeader` for the sender name and `MessageFooter` for delivery or read status. Both sit flush without horizontal padding.

```tsx
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import {
  Message,
  MessageContent,
  MessageFooter,
  MessageHeader,
} from "@/components/ui/message"

export function Example() {
  return (
    <div className="flex w-full flex-col gap-3">
      <Message>
        <MessageContent>
          <MessageHeader>Olivia</MessageHeader>
          <Bubble variant="secondary">
            <BubbleContent>I already checked the logs.</BubbleContent>
          </Bubble>
          <Bubble variant="secondary">
            <BubbleContent>
              Send the report to the team. Ping @team if you need help.
            </BubbleContent>
          </Bubble>
          <MessageFooter>Read Yesterday</MessageFooter>
        </MessageContent>
      </Message>
    </div>
  )
}
```

```tsx
<Message>
  <MessageContent>
    <MessageHeader>Olivia</MessageHeader>
    <Bubble variant="secondary">
      <BubbleContent>I already checked the logs.</BubbleContent>
    </Bubble>
    <MessageFooter>Read yesterday</MessageFooter>
  </MessageContent>
</Message>
```

## Actions

Place copy, retry, or feedback controls in `MessageFooter`. Give icon-only buttons an `aria-label`.

```tsx
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Button } from "@/components/ui/button"
import {
  Message,
  MessageContent,
  MessageFooter,
} from "@/components/ui/message"

function CopyIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
      <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
    </svg>
  )
}

function RefreshIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
      <path d="M21 3v5h-5" />
      <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
      <path d="M8 16H3v5" />
    </svg>
  )
}

export function Example() {
  return (
    <div className="flex w-full flex-col gap-3">
      <Message>
        <MessageContent>
          <Bubble variant="secondary">
            <BubbleContent>
              The install failure is coming from the workspace package.
            </BubbleContent>
          </Bubble>
          <Bubble variant="secondary">
            <BubbleContent>Okay drop me a link. Taking a look…</BubbleContent>
          </Bubble>
          <MessageFooter className="gap-1">
            <Button variant="ghost" size="icon-xs" aria-label="Copy">
              <CopyIcon />
            </Button>
            <Button variant="ghost" size="icon-xs" aria-label="Retry">
              <RefreshIcon />
            </Button>
            <span className="ml-1 text-destructive">Failed to send</span>
          </MessageFooter>
        </MessageContent>
      </Message>
    </div>
  )
}
```

```tsx
<MessageFooter className="gap-1">
  <Button variant="ghost" size="icon-xs" aria-label="Copy">
    <CopyIcon />
  </Button>
  <Button variant="ghost" size="icon-xs" aria-label="Retry">
    <RefreshIcon />
  </Button>
</MessageFooter>
```

## Attachment

Place [Attachment](/docs/components/attachment) below the bubble for files and downloads.

```tsx
import {
  Attachment,
  AttachmentAction,
  AttachmentActions,
  AttachmentContent,
  AttachmentDescription,
  AttachmentMedia,
  AttachmentTitle,
} from "@/components/ui/attachment"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Message, MessageContent } from "@/components/ui/message"

function FileTextIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
      <path d="M14 2v4a2 2 0 0 0 2 2h4" />
      <path d="M10 9H8" />
      <path d="M16 13H8" />
      <path d="M16 17H8" />
    </svg>
  )
}

function DownloadIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
      <polyline points="7 10 12 15 17 10" />
      <line x1="12" x2="12" y1="15" y2="3" />
    </svg>
  )
}

export function Example() {
  return (
    <div className="flex w-full flex-col gap-3">
      <Message>
        <MessageContent>
          <Bubble variant="secondary">
            <BubbleContent>
              Here's the image. Can you add it to the PDF? Use it for the cover page.
            </BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message align="end">
        <MessageContent>
          <Bubble variant="default" align="end">
            <BubbleContent>
              Done. Here's the PDF with the image added as the cover page.
            </BubbleContent>
          </Bubble>
          <Attachment className="max-w-xs">
            <AttachmentMedia>
              <FileTextIcon />
            </AttachmentMedia>
            <AttachmentContent>
              <AttachmentTitle>sales-dashboard.pdf</AttachmentTitle>
              <AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
            </AttachmentContent>
            <AttachmentActions>
              <AttachmentAction aria-label="Download sales-dashboard.pdf">
                <DownloadIcon />
              </AttachmentAction>
            </AttachmentActions>
          </Attachment>
        </MessageContent>
      </Message>
      <Message>
        <MessageContent>
          <Bubble variant="secondary">
            <BubbleContent>Thanks. Looks good.</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
    </div>
  )
}
```

## Accessibility

`Message` is presentational, so its accessibility comes from the content inside it.

**Icon-only actions** — give each footer control an `aria-label`.

```tsx
<MessageFooter>
  <Button variant="ghost" size="icon-xs" aria-label="Copy">
    <CopyIcon />
  </Button>
</MessageFooter>
```

**Status updates** — for in-progress rows, use [Marker](/docs/components/marker) with `role="status"`.

```tsx
<Message>
  <Marker role="status">
    <MarkerIcon>
      <Spinner />
    </MarkerIcon>
    <MarkerContent>Checking the logs…</MarkerContent>
  </Marker>
</Message>
```

## API Reference

### Message

Wrapper for a message row. Standard HTML attributes pass through to the root.

| Prop | Type | Default |
| --- | --- | --- |
| align | "start" \| "end" | "start" |
| className | string | — |

```tsx
<Message align="end">
  <MessageAvatar />
  <MessageContent />
</Message>
```

### MessageGroup

Groups consecutive messages from the same sender.

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<MessageGroup>
  <Message>...</Message>
  <Message>...</Message>
</MessageGroup>
```

### MessageAvatar

Avatar slot. Vertically centered on the bubble’s first text line — not the full multi-line height. Accepts native `div` props.

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<MessageAvatar>
  <Avatar size="sm">
    <AvatarImage src="/assets/brand/demos/avatars/avatar-1.png" alt="Mina Cho" />
    <AvatarFallback>MC</AvatarFallback>
  </Avatar>
</MessageAvatar>
```

### MessageContent

Wraps the header, message surface, and footer.

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<MessageContent>
  <MessageHeader>Olivia</MessageHeader>
  <Bubble>
    <BubbleContent>How can I help you today?</BubbleContent>
  </Bubble>
  <MessageFooter>Delivered</MessageFooter>
</MessageContent>
```

### MessageHeader

Content above the message, such as a sender name. Flush start, no horizontal padding. Stays start-aligned regardless of `align`.

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<MessageHeader>Olivia</MessageHeader>
```

### MessageFooter

Content below the message, such as status or actions. Flush to the message side, no horizontal padding. Follows `align` on the row.

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<MessageFooter>Delivered</MessageFooter>
```

## Source

```tsx
import type { HTMLAttributes, ReactNode } from "react";
import { cn } from "@/lib/utils";

type MessageAlign = "start" | "end";

type MessageGroupProps = HTMLAttributes<HTMLDivElement> & {
  children?: ReactNode;
};

export function MessageGroup({ className, ...props }: MessageGroupProps) {
  return (
    <div
      data-slot="message-group"
      className={cn("flex min-w-0 flex-col gap-2", className)}
      {...props}
    />
  );
}

type MessageProps = HTMLAttributes<HTMLDivElement> & {
  align?: MessageAlign;
  children?: ReactNode;
};

export function Message({
  className,
  align = "start",
  ...props
}: MessageProps) {
  return (
    <div
      data-slot="message"
      data-align={align}
      className={cn(
        "group/message relative flex w-full min-w-0 items-start gap-2 text-sm leading-relaxed tracking-wide data-[align=end]:flex-row-reverse",
        className,
      )}
      {...props}
    />
  );
}

type MessageAvatarProps = HTMLAttributes<HTMLDivElement> & {
  children?: ReactNode;
};

export function MessageAvatar({
  className,
  children,
  ...props
}: MessageAvatarProps) {
  return (
    <div
      data-slot="message-avatar"
      className={cn(
        // BubbleContent: 1px border + py-2, then center on the first text line.
        "flex w-fit min-w-8 shrink-0 justify-center pt-[calc(1px+0.5rem)] text-sm leading-relaxed group-has-data-[slot=message-header]/message:pt-[calc(1rem+0.625rem+1px+0.5rem)]",
        className,
      )}
      {...props}
    >
      <div className="flex h-[1lh] items-center justify-center">{children}</div>
    </div>
  );
}

type MessageContentProps = HTMLAttributes<HTMLDivElement> & {
  children?: ReactNode;
};

export function MessageContent({ className, ...props }: MessageContentProps) {
  return (
    <div
      data-slot="message-content"
      className={cn(
        "flex w-full min-w-0 flex-col gap-2.5 break-words group-data-[align=end]/message:*:data-slot:self-end group-data-[align=end]/message:*:data-[slot=message-header]:self-start",
        className,
      )}
      {...props}
    />
  );
}

type MessageHeaderProps = HTMLAttributes<HTMLDivElement> & {
  children?: ReactNode;
};

export function MessageHeader({ className, ...props }: MessageHeaderProps) {
  return (
    <div
      data-slot="message-header"
      className={cn(
        "flex max-w-full min-w-0 items-center self-start text-xs font-medium tracking-wide text-muted-foreground",
        className,
      )}
      {...props}
    />
  );
}

type MessageFooterProps = HTMLAttributes<HTMLDivElement> & {
  children?: ReactNode;
};

export function MessageFooter({ className, ...props }: MessageFooterProps) {
  return (
    <div
      data-slot="message-footer"
      className={cn(
        "flex max-w-full min-w-0 items-center text-xs font-medium tracking-wide text-muted-foreground group-data-[align=end]/message:justify-end",
        className,
      )}
      {...props}
    />
  );
}
```
