message

Displays a message in a conversation, with optional avatar, header, footer, and alignment.

PreviousNext
SR @srizzonDeploying to prod real quick. LG @luizgomessIt's 4:55 PM. On a Friday. SR @srizzonIt's a one-line change.Delivered LG @luizgomessIt's always a one-line change 😭.Alright, let me take a look.👍
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardAvatarComponent } from '@/shared/components/avatar';
import { ZardBubbleImports } from '@/shared/components/bubble/bubble.imports';
import { ZardMessageImports } from '@/shared/components/message/message.imports';

@Component({
  selector: 'z-demo-message-preview',
  imports: [ZardAvatarComponent, ...ZardBubbleImports, ...ZardMessageImports],
  template: `
    <div class="flex w-full max-w-sm flex-col gap-6 py-12">
      <z-message zAlign="end">
        <z-message-avatar>
          <z-avatar zSrc="https://github.com/srizzon.png" zAlt="@srizzon" zFallback="SR" />
        </z-message-avatar>
        <z-message-content>
          <z-bubble>
            <z-bubble-content>Deploying to prod real quick.</z-bubble-content>
          </z-bubble>
        </z-message-content>
      </z-message>

      <z-message>
        <z-message-avatar>
          <z-avatar zSrc="https://github.com/Luizgomess.png" zAlt="@luizgomess" zFallback="LG" />
        </z-message-avatar>
        <z-message-content>
          <z-bubble zVariant="muted">
            <z-bubble-content>It's 4:55 PM. On a Friday.</z-bubble-content>
          </z-bubble>
        </z-message-content>
      </z-message>

      <z-message zAlign="end">
        <z-message-avatar>
          <z-avatar zSrc="https://github.com/srizzon.png" zAlt="@srizzon" zFallback="SR" />
        </z-message-avatar>
        <z-message-content>
          <z-bubble>
            <z-bubble-content>It's a one-line change.</z-bubble-content>
          </z-bubble>
          <z-message-footer>Delivered</z-message-footer>
        </z-message-content>
      </z-message>

      <z-message>
        <z-message-avatar>
          <z-avatar zSrc="https://github.com/Luizgomess.png" zAlt="@luizgomess" zFallback="LG" />
        </z-message-avatar>
        <z-message-content>
          <z-bubble-group>
            <z-bubble zVariant="muted">
              <z-bubble-content>It's always a one-line change 😭.</z-bubble-content>
            </z-bubble>
            <z-bubble zVariant="muted">
              <z-bubble-content>Alright, let me take a look.</z-bubble-content>
              <z-bubble-reactions role="img" aria-label="Reactions: thumbs up">
                <span>👍</span>
              </z-bubble-reactions>
            </z-bubble>
          </z-bubble-group>
        </z-message-content>
      </z-message>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: { class: 'contents' },
})
export class ZardDemoMessagePreviewComponent {}

Installation

Copy
npx zard-cli@latest add message

Usage

import { ZardMessageImports } from '@/shared/components/message/message.imports';
Copy
<z-message zSrc="https://github.com/srizzon.png" zAlt="@srizzon" zVariant="muted">
  How can I help you today?
</z-message>
Copy

Examples

avatar

Project a real z-avatar into z-message-avatar to render an avatar next to the turn. Set zAlign="end" on z-message to align the avatar, and the bubble it sits beside, to the end of the row.
LG @luizgomessThe build failed during dependency installation. SR @srizzonCan you share the exact error? LG @luizgomessHere's the error from the logs Something went wrong with the build. The libraries are not installed correctly. Try running the build again.
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardAvatarComponent } from '@/shared/components/avatar';
import { ZardBubbleImports } from '@/shared/components/bubble/bubble.imports';
import { ZardMessageImports } from '@/shared/components/message/message.imports';

@Component({
  selector: 'z-demo-message-avatar',
  imports: [ZardAvatarComponent, ...ZardBubbleImports, ...ZardMessageImports],
  template: `
    <div class="flex w-full max-w-sm flex-col gap-6 py-12">
      <z-message>
        <z-message-avatar>
          <z-avatar zSrc="https://github.com/Luizgomess.png" zAlt="@luizgomess" zFallback="LG" />
        </z-message-avatar>
        <z-message-content>
          <z-bubble zVariant="muted">
            <z-bubble-content>The build failed during dependency installation.</z-bubble-content>
          </z-bubble>
        </z-message-content>
      </z-message>

      <z-message zAlign="end">
        <z-message-avatar>
          <z-avatar zSrc="https://github.com/srizzon.png" zAlt="@srizzon" zFallback="SR" />
        </z-message-avatar>
        <z-message-content>
          <z-bubble>
            <z-bubble-content>Can you share the exact error?</z-bubble-content>
          </z-bubble>
        </z-message-content>
      </z-message>

      <z-message>
        <z-message-avatar>
          <z-avatar zSrc="https://github.com/Luizgomess.png" zAlt="@luizgomess" zFallback="LG" />
        </z-message-avatar>
        <z-message-content>
          <z-bubble-group>
            <z-bubble zVariant="muted">
              <z-bubble-content>Here's the error from the logs</z-bubble-content>
            </z-bubble>
            <z-bubble zVariant="muted">
              <z-bubble-content>
                Something went wrong with the build. The libraries are not installed correctly. Try running the build
                again.
              </z-bubble-content>
            </z-bubble>
          </z-bubble-group>
        </z-message-content>
      </z-message>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: { class: 'contents' },
})
export class ZardDemoMessageAvatarComponent {}

group

Wrap consecutive z-message elements from the same sender in z-message-group to stack them tightly. Leave z-message-avatar empty on the earlier turns — it reserves the same width as the real avatar on the last one, so the group stays aligned.
I checked the registry addresses. LG @luizgomessThe component and example JSON now live under the UI registry.
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardAvatarComponent } from '@/shared/components/avatar';
import { ZardBubbleImports } from '@/shared/components/bubble/bubble.imports';
import { ZardMessageImports } from '@/shared/components/message/message.imports';

@Component({
  selector: 'z-demo-message-group',
  imports: [ZardAvatarComponent, ...ZardBubbleImports, ...ZardMessageImports],
  template: `
    <div class="flex w-full max-w-sm flex-col gap-6 py-12">
      <z-message-group>
        <z-message>
          <z-message-avatar />
          <z-message-content>
            <z-bubble zVariant="muted">
              <z-bubble-content>I checked the registry addresses.</z-bubble-content>
            </z-bubble>
          </z-message-content>
        </z-message>
        <z-message>
          <z-message-avatar>
            <z-avatar zSrc="https://github.com/Luizgomess.png" zAlt="@luizgomess" zFallback="LG" />
          </z-message-avatar>
          <z-message-content>
            <z-bubble zVariant="muted">
              <z-bubble-content>The component and example JSON now live under the UI registry.</z-bubble-content>
            </z-bubble>
          </z-message-content>
        </z-message>
      </z-message-group>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: { class: 'contents' },
})
export class ZardDemoMessageGroupComponent {}

header and footer

Project z-message-header for a sender name and z-message-footer for metadata such as a delivery or read status. The footer follows the message's zAlign side, so it stays right-aligned on a sent turn.
OliviaI already checked the logs.Send the report to the team. Ping @srizzon if you need help.
Read Yesterday
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardBubbleImports } from '@/shared/components/bubble/bubble.imports';
import { ZardMessageImports } from '@/shared/components/message/message.imports';

@Component({
  selector: 'z-demo-message-header-and-footer',
  imports: [...ZardBubbleImports, ...ZardMessageImports],
  template: `
    <div class="flex w-full max-w-sm flex-col gap-8 py-12">
      <z-message>
        <z-message-content>
          <z-message-header>Olivia</z-message-header>
          <z-bubble zVariant="muted">
            <z-bubble-content>I already checked the logs.</z-bubble-content>
          </z-bubble>
        </z-message-content>
      </z-message>

      <z-message zAlign="end">
        <z-message-content>
          <z-bubble>
            <z-bubble-content>Send the report to the team. Ping &#64;srizzon if you need help.</z-bubble-content>
          </z-bubble>
          <z-message-footer>
            <div>
              Read
              <span class="font-normal">Yesterday</span>
            </div>
          </z-message-footer>
        </z-message-content>
      </z-message>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: { class: 'contents' },
})
export class ZardDemoMessageHeaderAndFooterComponent {}

actions

Place message-level actions — copy, retry, like or dislike — inside z-message-footer as real z-buttons. Give each icon-only button an aria-label, since the footer holds no visible text for it.
The install failure is coming from the workspace package.Okay drop me a link. Taking a look...Failed to send
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideCopy, lucideRefreshCcw, lucideThumbsDown, lucideThumbsUp } from '@ng-icons/lucide';

import { ZardBubbleImports } from '@/shared/components/bubble/bubble.imports';
import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardMessageImports } from '@/shared/components/message/message.imports';

@Component({
  selector: 'z-demo-message-actions',
  imports: [NgIcon, ZardButtonComponent, ...ZardBubbleImports, ...ZardMessageImports],
  template: `
    <div class="flex w-full max-w-sm flex-col gap-8 py-12">
      <z-message>
        <z-message-content>
          <z-bubble zVariant="muted">
            <z-bubble-content>The install failure is coming from the workspace package.</z-bubble-content>
          </z-bubble>
          <z-message-footer>
            <button type="button" z-button zType="ghost" zSize="icon" aria-label="Copy" title="Copy">
              <ng-icon name="lucideCopy" />
            </button>
            <button type="button" z-button zType="ghost" zSize="icon" aria-label="Like" title="Like">
              <ng-icon name="lucideThumbsUp" />
            </button>
            <button type="button" z-button zType="ghost" zSize="icon" aria-label="Dislike" title="Dislike">
              <ng-icon name="lucideThumbsDown" />
            </button>
          </z-message-footer>
        </z-message-content>
      </z-message>

      <z-message zAlign="end">
        <z-message-content>
          <z-bubble>
            <z-bubble-content>Okay drop me a link. Taking a look...</z-bubble-content>
          </z-bubble>
          <z-message-footer class="gap-2">
            <span class="text-destructive font-normal">Failed to send</span>
            <button type="button" z-button zType="ghost" zSize="icon-xs" aria-label="Retry" title="Retry">
              <ng-icon name="lucideRefreshCcw" />
            </button>
          </z-message-footer>
        </z-message-content>
      </z-message>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  viewProviders: [provideIcons({ lucideCopy, lucideRefreshCcw, lucideThumbsDown, lucideThumbsUp })],
  host: { class: 'contents' },
})
export class ZardDemoMessageActionsComponent {}

attachment

Project a real z-item next to the bubble inside z-message-content to attach media or a document to a turn — zVariant="image" for a photo, zVariant="icon" for a file — placed before or after the bubble depending on reading order.
Cover pageHere's the image. Can you add it to the PDF? Use it for the cover page.Done. Here's the PDF with the image added as the cover page.sales-dashboard.pdfPDF · 2.4 MBThanks. Looks good.
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideDownload, lucideFileText } from '@ng-icons/lucide';

import { ZardBubbleImports } from '@/shared/components/bubble/bubble.imports';
import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardItemImports } from '@/shared/components/item/item.imports';
import { ZardMessageImports } from '@/shared/components/message/message.imports';

@Component({
  selector: 'z-demo-message-attachment',
  imports: [NgIcon, ZardButtonComponent, ...ZardBubbleImports, ...ZardItemImports, ...ZardMessageImports],
  template: `
    <div class="flex w-full max-w-sm flex-col gap-8 py-12">
      <z-message zAlign="end">
        <z-message-content>
          <z-item zVariant="outline" class="w-fit rounded-2xl p-1.5">
            <z-item-media zVariant="image" class="size-24 rounded-xl">
              <img src="/images/github_banner.png" alt="Cover page" />
            </z-item-media>
          </z-item>
          <z-bubble>
            <z-bubble-content>Here's the image. Can you add it to the PDF? Use it for the cover page.</z-bubble-content>
          </z-bubble>
        </z-message-content>
      </z-message>

      <z-message>
        <z-message-content>
          <z-bubble zVariant="muted">
            <z-bubble-content>Done. Here's the PDF with the image added as the cover page.</z-bubble-content>
          </z-bubble>
          <z-item zVariant="muted" class="w-fit rounded-2xl">
            <z-item-media zVariant="icon" class="bg-background size-9 rounded-xl">
              <ng-icon name="lucideFileText" />
            </z-item-media>
            <z-item-content>
              <z-item-title>sales-dashboard.pdf</z-item-title>
              <z-item-description>PDF · 2.4 MB</z-item-description>
            </z-item-content>
            <z-item-actions>
              <button
                type="button"
                z-button
                zType="secondary"
                zSize="icon-sm"
                zShape="circle"
                aria-label="Download"
                title="Download"
              >
                <ng-icon name="lucideDownload" />
              </button>
            </z-item-actions>
          </z-item>
        </z-message-content>
      </z-message>

      <z-message zAlign="end">
        <z-message-content>
          <z-bubble>
            <z-bubble-content>Thanks. Looks good.</z-bubble-content>
          </z-bubble>
        </z-message-content>
      </z-message>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  viewProviders: [provideIcons({ lucideDownload, lucideFileText })],
  host: { class: 'contents' },
})
export class ZardDemoMessageAttachmentComponent {}

shorthand

A zard-only shorthand: a z-message with no projected z-message-content builds the avatar, bubble and header/footer itself from zSrc/zFallback, zVariant, zHeader and zFooter, so a plain turn is one tag. Project the explicit z-message-content (with z-message-header/z-message-footer around a z-bubble) when the turn needs a custom composition.
@luizgomess
How can I help you today?
@srizzon
Send me the release notes.
Delivered
OL Olivia
The notes are in the shared doc.
Read Yesterday
@srizzonProject the content when the turn needs more than a bubble.
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardBubbleImports } from '@/shared/components/bubble/bubble.imports';
import { ZardMessageImports } from '@/shared/components/message/message.imports';

@Component({
  selector: 'z-demo-message-shorthand',
  imports: [...ZardBubbleImports, ...ZardMessageImports],
  template: `
    <div class="flex w-full max-w-sm flex-col gap-6 py-12">
      <z-message zSrc="https://github.com/Luizgomess.png" zAlt="@luizgomess" zVariant="muted">
        How can I help you today?
      </z-message>

      <z-message zAlign="end" zSrc="https://github.com/srizzon.png" zAlt="@srizzon" zFooter="Delivered">
        Send me the release notes.
      </z-message>

      <z-message zFallback="OL" zVariant="muted" zHeader="Olivia" zFooter="Read Yesterday">
        The notes are in the shared doc.
      </z-message>

      <z-message zAlign="end" zSrc="https://github.com/srizzon.png" zAlt="@srizzon">
        <z-message-content>
          <z-bubble zVariant="outline">
            <z-bubble-content>Project the content when the turn needs more than a bubble.</z-bubble-content>
          </z-bubble>
        </z-message-content>
      </z-message>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: { class: 'contents' },
})
export class ZardDemoMessageShorthandComponent {}

API Reference

z-messageComponent

The message row wrapper. It owns the avatar, alignment, header and footer around the message surface.

PropertyDescriptionTypeDefault
[zAlign] The alignment of the message in the conversation. start | end start
[zSrc] Avatar image of the sender. Renders the avatar slot for you; a projected z-message-avatar wins over it. string | SafeUrl -
[zAlt] Alternative text of the shorthand avatar image. string -
[zFallback] Initials shown while the shorthand avatar has no image, or instead of one. string -
[zHeader] Content above the turn, such as a sender name. Only applies to the shorthand. string | TemplateRef<void> -
[zFooter] Content below the turn, such as a delivery status. Only applies to the shorthand. string | TemplateRef<void> -
[zVariant] Variant of the bubble the shorthand renders. Ignored once the content is projected. default | secondary | muted | tinted | outline | ghost | destructive default
[class] Additional classes to apply to the row. ClassValue -

z-message-avatarComponent

The avatar slot, aligned to the bottom of the message. When the message has a z-message-footer, the avatar shifts up to stay aligned with the message surface instead of the footer.

PropertyDescriptionTypeDefault
[class] Additional classes to apply to the avatar slot. ClassValue -

z-message-contentComponent

Wraps the header, message surface and footer.

PropertyDescriptionTypeDefault
[class] Additional classes to apply to the content slot. ClassValue -

z-message-headerComponent

Displays content above the message, such as a sender name. Stays aligned to the start regardless of zAlign.

PropertyDescriptionTypeDefault
[zHeader] Header content, as an alternative to projecting it. string | TemplateRef<void> -
[class] Additional classes to apply to the header. ClassValue -

z-message-footerComponent

Displays content below the message, such as status or actions. Aligns to the message side.

PropertyDescriptionTypeDefault
[zFooter] Footer content, as an alternative to projecting it. string | TemplateRef<void> -
[class] Additional classes to apply to the footer. ClassValue -

z-message-groupComponent

Groups consecutive messages from the same sender.

PropertyDescriptionTypeDefault
[class] Additional classes to apply to the group root. ClassValue -
github iconwhatsapp icondiscord iconX icon

Made with ❤ in Brazil. Open source and available on GitHub .