Chat thread — Usage

Overview

ChatThread + ChatBubble are the canonical message bubble of the design system (canon since 17 Oct 2026, from prototype proto/mail-konverzace@6355a0399). The same bubble renders a 1:1 chat (Communication › Chats: Telegram, WhatsApp, SMS) and an e-mail conversation view.

Anatomy of one message row (.cht-msg.in|out):

  1. Bubble (.cht-bubble) — a box, not a line: incoming --line-100, outgoing solid --accent with --fg-on-accent; --radius-xl with a sharp --radius-xs corner at the bottom on the sender's side; padding --sp-3 --sp-5. Inside: the text, then the attachments.
  2. Attachments (.cht-atts) — INSIDE the bubble, UNDER the text.
  3. Meta row (.cht-msg-m) — BELOW the bubble: avatar (other party), name, time, delivery state (outgoing), host actions ("Original"). The clamp toggle is not a meta action: it sits inside the bubble (more).

Max width 80% of the thread (phone 85%). Axes (ACRM-527): the bubble is a box — its edge sits on the box axis; the component carries no negative margin, a host that needs the bubble to hang onto its page's box axis shifts it itself.

This is not the Discussion candidate (project comment threads: flat rows, no bubbles).

Components

  • ChatThread — scrolling log container (role="log", flex column, --sp-4 gap). Day separators are plain .cht-day text the host renders between rows (no ChatDay component). The e-mail conversation view may use its own flex-column container (role="list") with ChatBubble rows — the row only needs a flex-column parent.
  • ChatBubble — one message row. Props:
  • text — ReactNode. A plain string keeps line breaks (pre-wrap); formatted content (paragraphs, bold, sanitised HTML via dangerouslySetInnerHTML on a child) goes with rich. Empty / null text draws no text block (attachment-only bubble).
  • rich — formatted content: no pre-wrap, --sp-3 between <p>.
  • clampLines + expanded + textRef — clamp long text to N lines; the host reads textRef.current.scrollHeight > clientHeight to decide whether to offer "Show all" (via more) and flips expanded. Attachments are never clamped.
  • more — the "Show all" / "Show less" toggle INSIDE the bubble, directly under the text (before attachments), as a text button .cht-more with aria-expanded (white underlined in the accent bubble). Chat-app convention; short messages render none.
  • attachments — node rendered inside the bubble under the text. Chat uses DS Attachment; e-mail uses .cht-att pills (22 px pill, --surface-100).
  • time (pre-formatted), out, fromLabel (pre-translated sender, required).
  • senderName + avatar — visible sender in the meta row (group threads, e-mail; usually only the other party gets an avatar). senderName is aria-hidden: the screen reader already has fromLabel.
  • state ('sending' | 'sent' | 'delivered' | 'read' | 'failed'), stateLabel, errorText — delivery state of an outgoing message, shown in the meta row.
  • metaExtra — small meta after the time (e.g. publish tag); actions — host buttons at the end of the meta row (use .cht-link: --small, --fg-muted, underline on hover/focus).
  • published, className.

I18n

The package does not format dates, translate labels, or know about locale. The host supplies time, fromLabel, senderName, stateLabel and action labels already formatted/translated.

Delivery status

stateicontone
sendingclockdefault
sentcheck (single tick)default
deliveredchecks (double tick)default
readchecks (double tick)accent
failedalert-circlecritical

The icon renders only when out is true and state is set. Always pass stateLabel with state. The state lives in the bubble's meta row — never a separate status line.

Voice and video messages

VoiceMessage / VideoNote / VideoMessage use the same row (.cht-msg) with their own bubble content. Outgoing voice/video is solid --accent like every outgoing bubble (AD 17 Oct 2026, proto/bublina-hlas-ajda@1740bc21d); the player carries --fg-on-accent on it (see Voice message). An attachment inside an outgoing bubble is transparent with a white (--fg-on-accent) frame, text and icons; its hover is a 15 % white veil. A bubble that ends with attachments gets padding-bottom: calc(var(--sp-3) + var(--sp-2)) so the gap above and below looks equal.

Do / Don't

  • Do pass fromLabel for every bubble, even your own ("Já"/"You").
  • Do keep text as real DOM content (never an aria-label) so it stays findable/translatable.
  • Do put attachments in attachments (inside the bubble) and message actions in actions (meta row) — never a button inside the bubble text.
  • Don't draw an extra frame around the text inside the bubble (the bubble is the one box).
  • Don't use it for internal comment threads — that is Discussion.
  • Don't invent a "visually hidden" class; use .acrm-sr.

Source: docs/discussion/chat-thread.usage.md · GitLab