Message
A chat message: the bubble, who sent it and when. Incoming at the start, outgoing at the end in brand.
Default
Incoming at the start with the sender's avatar; your own at the end, in brand.
html
<div class="vt-message">
<span class="vt-avatar vt-avatar-sm">MK</span>
<div class="vt-message-body">
<div class="vt-message-bubble">Did you get a chance to look at the pricing page?</div>
<div class="vt-message-meta">10:02</div>
</div>
</div>
<div class="vt-message vt-message-out">
<div class="vt-message-body">
<div class="vt-message-bubble">Yes — the Pro card should be highlighted by default.</div>
<div class="vt-message-meta">10:04</div>
</div>
</div>
Grouped
Messages one sender writes in a row are one vt-message with several bubbles: the avatar and the time show once.
html
<div class="vt-message">
<span class="vt-avatar vt-avatar-sm">MK</span>
<div class="vt-message-body">
<div class="vt-message-bubble">Agreed. I’ll add the border and the badge.</div>
<div class="vt-message-bubble">Also updated the FAQ copy, can you proofread?</div>
<div class="vt-message-meta">10:05</div>
</div>
</div>
<div class="vt-message vt-message-out">
<div class="vt-message-body">
<div class="vt-message-bubble">Done. Two typos fixed.</div>
<div class="vt-message-bubble">Otherwise perfect.</div>
<div class="vt-message-meta">10:12</div>
</div>
</div>
With Author
In a group chat the meta goes above the bubbles and names the sender with vt-message-author.
html
<div class="vt-message">
<span class="vt-avatar vt-avatar-sm">OL</span>
<div class="vt-message-body">
<div class="vt-message-meta"><span class="vt-message-author">Ola Nowak</span> 10:21</div>
<div class="vt-message-bubble">New tokens are in Figma.</div>
</div>
</div>
<div class="vt-message">
<span class="vt-avatar vt-avatar-sm">LE</span>
<div class="vt-message-body">
<div class="vt-message-meta"><span class="vt-message-author">Leo Park</span> 10:23</div>
<div class="vt-message-bubble">Campaign draft attached — feedback by Friday?</div>
</div>
</div>
Subtle Outgoing
In an assistant chat the brand belongs to the assistant, so the user's messages stay neutral: vt-message-subtle with vt-message-out.
html
<div class="vt-message vt-message-out vt-message-subtle">
<div class="vt-message-body">
<div class="vt-message-bubble">Write a regex that validates a semver string.</div>
</div>
</div>
Typing
The typing indicator is a bubble holding three vt-dot vt-dot-sm, role=status with a label.
html
<div class="vt-message">
<span class="vt-avatar vt-avatar-sm">MK</span>
<div class="vt-message-body">
<div class="vt-message-bubble" role="status" aria-label="Maria Kim is typing">
<span class="vt-dot vt-dot-sm vt-animate-pulse" aria-hidden="true"></span>
<span class="vt-dot vt-dot-sm vt-animate-pulse" aria-hidden="true"></span>
<span class="vt-dot vt-dot-sm vt-animate-pulse" aria-hidden="true"></span>
</div>
</div>
</div>
Guidelines
Do
Don't
Anatomy
div.vt-message(.vt-message-out) ← One turn of the conversation
├── span.vt-avatar.vt-avatar-sm ← Sender (incoming)
└── div.vt-message-body
├── div.vt-message-bubble ← A message (one or more in a row)
└── div.vt-message-meta ← Time, optionally the author
└── span.vt-message-author
API Reference
| Class | Type | Description |
|---|---|---|
vt-message |
Base | One turn: an optional avatar and the body in a row, 8px apart; incoming — at the start of the row |
vt-message-out |
Direction | Outgoing — at the end of the row, the bubbles in brand-fill with on-brand text |
vt-message-subtle |
Tone | With vt-message-out: the outgoing bubble stays neutral (surface-subtle) — the user in an assistant chat |
vt-message-body |
Part | The bubbles and the meta stacked 4px apart; at most 80% of the row and 448px wide |
vt-message-bubble |
Part | One message — Body/SM, 8/16px padding, radius 2xl, surface-subtle |
vt-message-meta |
Part | Time (and author) — Caption, muted; under the bubbles, or above them in a group chat |
vt-message-author |
Part | Sender name inside the meta — Label/SM, secondary |
vt-avatar vt-avatar-sm |
Part | Sender avatar, first in the row; left out for your own messages |
Accessibility
| Feature | Details |
|---|---|
| Thread | The list of messages is a region the user can find (a main or a section with a label); new messages can be announced with role="log" on it |
| Sender | The avatar image carries the sender's name in alt, or the meta names the author; direction is not told by position alone |
| Typing | The typing bubble is role="status" with an aria-label; its dots are aria-hidden |
| Contrast | Outgoing text is on-brand on brand-fill — the pair every theme keeps at AA |
Built with: