Skip to content

Bubble

A chat message bubble with left and right alignment, semantic color variants, and optional previews for image, audio, file and link attachments. The bubble is only the message surface. Avatars, sender rows and message lists are composed around it with the layout utilities and other components.

Usage

Every part of the bubble is optional. Place any combination of x-h-bubble-header, x-h-bubble-content, x-h-bubble-footer, attachment previews and x-h-bubble-reactions inside an x-h-bubble element. Use data-align="right" for sent messages and the default left alignment for received ones. A <time> element inside the header is automatically de-emphasized.

Accessibility

The bubble itself is a plain container and gets no role. Give the surrounding message list role="log" with aria-live="polite" so new messages are announced, and use <time datetime="..."> for timestamps. The reactions element defaults to role="group" with an accessible name of "Reactions", which you can localize with data-label or override with your own aria-label. Images must carry an alt attribute. Audio attachments render a custom player: the play/pause button and the seek slider (role="slider", operable with the arrow, Home and End keys) get accessible names you can localize with data-play-label, data-pause-label and data-label.

API Reference

Component attribute(s)

x-h-bubble
x-h-bubble-header
x-h-bubble-content
x-h-bubble-footer
x-h-bubble-image
x-h-bubble-gallery
x-h-bubble-gallery-more
x-h-bubble-audio
x-h-bubble-file
x-h-bubble-link
x-h-bubble-reactions

Attributes

x-h-bubble

AttributeTypeRequiredDescription
data-alignleft
right
falseMessage side. Defaults to left (received). Use right for sent ones.
data-variantprimary
secondary
warning
negative
outline
transparent
falseSemantic color style. Defaults to secondary.

x-h-bubble-audio

Apply to an <audio> element with a src (or a <source> child). It renders a custom player (play/pause button, seek slider and time readout) and hides the native controls.

AttributeTypeRequiredDescription
data-play-labelstringfalseAccessible name for the play button. Defaults to Play.
data-pause-labelstringfalseAccessible name for the button while playing. Defaults to Pause.
data-labelstringfalseAccessible name for the seek slider. Defaults to Seek.

x-h-bubble-reactions

AttributeTypeRequiredDescription
data-labelstringfalseAccessible name for the reactions group. Defaults to Reactions.

Examples

Basic conversation

html
<div class="vbox w-full gap-2">
  <div x-h-bubble>
    <div x-h-bubble-header>John Doe <time datetime="11:46">11:46</time></div>
    <div x-h-bubble-content>That's awesome. I think our users will really appreciate the improvements.</div>
    <div x-h-bubble-footer>
      <svg x-h-icon data-icon="check" role="presentation"></svg>
      Delivered
    </div>
  </div>
  <div x-h-bubble data-align="right" data-variant="primary">
    <div x-h-bubble-header>You <time datetime="11:47">11:47</time></div>
    <div x-h-bubble-content>Thanks! The new version ships next week.</div>
  </div>
</div>

Variants

html
<div class="vbox w-full gap-2">
  <div x-h-bubble data-variant="primary">
    <div x-h-bubble-content>Primary</div>
  </div>
  <div x-h-bubble>
    <div x-h-bubble-content>Secondary (default)</div>
  </div>
  <div x-h-bubble data-variant="warning">
    <div x-h-bubble-content>Warning</div>
  </div>
  <div x-h-bubble data-variant="negative">
    <div x-h-bubble-content>Negative</div>
  </div>
  <div x-h-bubble data-variant="outline">
    <div x-h-bubble-content>Outline</div>
  </div>
  <div x-h-bubble data-variant="transparent">
    <div x-h-bubble-content>Transparent</div>
  </div>
</div>

With avatar

html
<div class="hbox w-full items-end gap-2">
  <span x-h-avatar>
    <img x-h-avatar-image src="/harmonia/logo/harmonia-square.svg" alt="@harmonia" />
  </span>
  <div x-h-bubble>
    <div x-h-bubble-header>Harmonia <time datetime="11:46">11:46</time></div>
    <div x-h-bubble-content>Welcome to the chat!</div>
  </div>
</div>

Image attachment

html
<div x-h-bubble>
  <div x-h-bubble-header>John Doe <time datetime="11:46">11:46</time></div>
  <img x-h-bubble-image class="w-3xs" src="/harmonia/photos/ignartonosbg-mountain.jpg" alt="Sheet component preview" />
  <div x-h-bubble-footer>Delivered</div>
</div>

INFO

Photos by ignartonosbg via Pixabay.

html
<div x-h-bubble>
  <div x-h-bubble-header>John Doe <time datetime="11:46">11:46</time></div>
  <div x-h-bubble-gallery class="w-3xs">
    <img x-h-bubble-image src="/harmonia/photos/ignartonosbg-fences.jpg" alt="Photo preview" />
    <img x-h-bubble-image src="/harmonia/photos/ignartonosbg-foxtail-grass.jpg" alt="Photo preview" />
    <img x-h-bubble-image src="/harmonia/photos/ignartonosbg-mountains.jpg" alt="Photo preview" />
    <img x-h-bubble-image src="/harmonia/photos/ignartonosbg-orange-juice.jpg" alt="Photo preview" />
  </div>
</div>

When a message carries more photos than the four shown, wrap the fourth tile in x-h-bubble-gallery-more and overlay a <button> with a label. The label text is yours to author, so it can be localized (for example +3 or 3 more). Give the button a descriptive aria-label, since the terse label alone does not convey the action.

html
<div x-h-bubble>
  <div x-h-bubble-header>John Doe <time datetime="11:46">11:46</time></div>
  <div x-h-bubble-gallery class="w-3xs">
    <img x-h-bubble-image src="/harmonia/photos/ignartonosbg-fences.jpg" alt="Photo preview" />
    <img x-h-bubble-image src="/harmonia/photos/ignartonosbg-foxtail-grass.jpg" alt="Photo preview" />
    <img x-h-bubble-image src="/harmonia/photos/ignartonosbg-mountains.jpg" alt="Photo preview" />
    <div x-h-bubble-gallery-more>
      <img x-h-bubble-image src="/harmonia/photos/ignartonosbg-orange-juice.jpg" alt="Photo preview" />
      <button aria-label="Show 3 more photos">+3</button>
    </div>
  </div>
</div>

Audio attachment

html
<div x-h-bubble>
  <div x-h-bubble-header>John Doe <time datetime="11:48">11:48</time></div>
  <audio x-h-bubble-audio src="/harmonia/audio/chime.wav"></audio>
</div>

File attachment

html
<div x-h-bubble>
  <div x-h-bubble-file>
    <svg x-h-icon data-icon="file" role="presentation"></svg>
    <div class="vbox min-w-0">
      <span class="truncate font-medium">Quarterly-report.pdf</span>
      <span class="text-xs opacity-75">PDF, 2.4 MB</span>
    </div>
    <button x-h-button data-size="icon-sm" data-variant="transparent" aria-label="Download">
      <svg x-h-icon data-icon="export" role="presentation"></svg>
    </button>
  </div>
  <div x-h-bubble-footer>Delivered</div>
</div>
html
<div x-h-bubble>
  <div x-h-bubble-content>Check out the project page</div>
  <a x-h-bubble-link class="w-3xs" href="https://github.com/codbex/codbex-harmonia" target="_blank">
    <img src="/harmonia/preview.png" alt="Harmonia project preview" />
    <div class="vbox gap-1 p-2">
      <span class="font-medium">Harmonia</span>
      <span class="hbox items-center gap-1 text-xs opacity-75">
        <svg x-h-icon class="size-3" data-icon="link" role="presentation"></svg>
        codbex.com/harmonia/
      </span>
    </div>
  </a>
</div>

Reactions

html
<div class="vbox w-full gap-2">
  <div x-h-bubble>
    <div x-h-bubble-content>We just shipped version 2.4!</div>
    <div x-h-bubble-reactions>
      <button>🎉 3</button>
      <button>👍 1</button>
    </div>
  </div>
  <div x-h-bubble data-align="right" data-variant="primary">
    <div x-h-bubble-content>Congratulations team!</div>
    <div x-h-bubble-reactions data-label="Reactions to your message">
      <button>❤️ 2</button>
    </div>
  </div>
</div>