ComponentsChat
Chat
atomA conversation thread — incoming vs your own message bubbles.
Conversation
A chat thread — the three voices: incoming, you, and the assistant.
Best practices
Do
- Pick the voice with the modifier: plain .msg for an incoming person, .msg--me for you, .msg--ai for the assistant’s turn.
- Let the AI turn (.msg--ai) hold its reasoning, tool receipts, answer and .msg__actions as one grouped answer-card.
- Keep .msg__actions as ghost icon buttons (copy · regenerate · rate) — they reveal on hover and stay visible on touch.
Don't
- Don't give an incoming person’s message the brand fill — that reads as “you.”
- Don't stack more than one primary action in the bubble; message actions are all quiet ghosts.
You09:24
Is the draft contract ready to sign off today?
Ledger AI09:24
Yes — the redline is clean and both parties initialled every change. You’re clear to sign.
Best practices
Do
- Pick the voice with the modifier: plain .msg for an incoming person, .msg--me for you, .msg--ai for the assistant’s turn.
- Let the AI turn (.msg--ai) hold its reasoning, tool receipts, answer and .msg__actions as one grouped answer-card.
- Keep .msg__actions as ghost icon buttons (copy · regenerate · rate) — they reveal on hover and stay visible on touch.
Don't
- Don't give an incoming person’s message the brand fill — that reads as “you.”
- Don't stack more than one primary action in the bubble; message actions are all quiet ghosts.
Recipe CSS
This is the exact CSS your kit ships for Chat — token-driven, so it re-themes with every knob. Get it (and the rest) from the configurator's Use kit panel.
/* === Chat message ===
A conversation bubble — a name/time header over the body, on the surface.
Stack them in a .thread (a plain vertical-rhythm container) for a chat or
comment feed. The modifier flips ONE axis: who is speaking.
.msg → an incoming message (left, neutral surface)
.msg--me → the sender's own message (right, brand-soft fill)
.msg--ai → the assistant's turn (a wider, raised answer-card that
groups its reasoning, tool receipts, reply + actions)
The bubble caps its width so a thread reads as a conversation, not full-bleed
paragraphs. Everything derives — fill, border and text all from tokens. */
.thread {
display: flex;
flex-direction: column;
gap: var(--k-space, var(--k-s-8));
}
.msg {
align-self: flex-start;
max-width: 85%;
border: var(--k-bw) solid var(--k-border);
border-radius: var(--k-radius-lg);
background: var(--k-surface);
padding: var(--k-s-10) var(--k-s-12);
}
.msg--me {
align-self: flex-end;
border-color: var(--k-primary-soft);
background: var(--k-primary-soft);
color: var(--k-primary-soft-fg);
}
.msg__head {
display: flex;
align-items: center;
gap: var(--k-s-8);
margin-bottom: var(--k-s-4);
}
.msg__name { font-size: var(--k-type-small); font-weight: var(--k-weight-semibold); }
/* CP6 — time pushes to the trailing edge so an optional leading .avatar groups
with the name (avatar + name left, time right). Was justify-content:space-between
on the head; an avatar there would have spread away from the name. */
.msg__time { font-size: var(--k-type-caption); color: var(--k-fg-muted); margin-left: auto; }
.msg--me .msg__time { color: inherit; opacity: 0.7; }
.msg__body { margin: 0; font-size: var(--k-type-small); line-height: 1.55; color: var(--k-fg-muted); }
.msg--me .msg__body { color: inherit; }
/* The assistant's turn — a distinct THIRD voice. It's not a narrow bubble: an
AI answer earns room, so it stretches and sits on a faintly raised surface
that groups its reasoning, tool receipts, reply and actions as one card
(the Claude/ChatGPT layout: compact user bubbles, full-width AI answers). */
.msg--ai {
align-self: stretch;
max-width: 100%;
background: var(--k-surface-raised, var(--k-surface));
/* An accent left-edge marks the assistant's turn so the answer-card reads as
distinct even where surface-raised collapses to the surface (flat Style). */
border-inline-start: 2px solid var(--k-primary);
}
/* Action row — copy / regenerate / rate an AI reply. Real ghost icon buttons
inside; quiet by default (muted), brightening on hover / keyboard focus. */
.msg__actions {
display: flex;
align-items: center;
gap: var(--k-s-2);
margin-top: var(--k-s-6);
opacity: 0.65;
transition: opacity var(--k-dur-fast, 140ms) var(--k-ease, ease);
}
.msg:hover .msg__actions,
.msg:focus-within .msg__actions { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .msg__actions { transition: none; } }