ComponentsChat

Chat

atom

A 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; } }