Chatbot Interface Design: Conversation Flow & Composer
Claude chat UI spec: message bubble radius, 300px collapse, thinking accordion timing, IME guard, / and @ commands, token gauge and MCP approval cards.
⬅️ Previous: Chapter 2: Atomic Components & Interactive Controls · 📚 Back to the 1.x overview · Next: Chapter 4: Motion Physics, Shaders & Ambient Light ➡️
This chapter documents the conversation layer of the Claude chat UI found in the Claude Desktop 1.x front-end build: message bubbles, the thinking collapse, the composer with its input-method guard and / and @ commands, the model switcher with its token gauge, and MCP tool approval cards. It is a translation of the Chinese handbook chapter; every value is copied from it unchanged, and code blocks are reproduced as-is.
Chapter 3: Conversation Flow & Command Input Engine
Technical domain:
Chat Stream & Composer Engine
Analysis baseline: a full AST-level analysis and style audit, with REA (Reverse Engineer Anything) v6.5.0, of the official macOS desktop production build/Applications/Claude.app(@ant/desktop v2.37337.1, build timestamp2026-10-13T22:16:06Z). No guesswork: real source code and CSS rules are quoted throughout.
Production evidence trail
| Area | Official production file hash | Key evidence symbols / rules |
|---|---|---|
| Message bubbles and text column | frame-shell-chrome-CAw-tYzh.js | [data-cds=UserMessage], [data-cds=AssistantMessage], --cds-message-bubble-radius, --cds-assistant-message-text-inset, --cds-assistant-message-text-stop |
| Conversation state machine and read collapse | shared-2-Cb74JByF.js | UX (UserMessage), bY, xY, yY, 300px truncation with a mask-image gradient mask, MessageActions roving focus |
| Thinking timeline and wave motion | cc1aae219-DtZTxCKj.css | .timeline-quiet-dots, @keyframes timeline-quiet-dot (1.9s), .timeline-expand (Grid 0fr), .timeline-status-* |
| Input-method composition race defense | shared-frame-BfjMqbuN.js | Lm(), Rm(), zm(), Bm(), Um(), WebKit 500ms Post-Composition Delta Guard, beforeinput interception |
| Slash and Mention syntax tree | cb6930dae-BD0W5ZM9.js | cs() lexer, Qo (@ mentions), $o (strips CJK full-width punctuation), rs = 1024, a 256-file reference circuit breaker |
| Token metering and cache lifetime | c058e28e3-DXZbxFF9.js, c30db9bec-DjF3pT_h.js | ephemeral_5m_input_tokens, ephemeral_1h_input_tokens, adaptive thinking architecture, 4-color cost breakdown |
| MCP tool authorization and human-in-the-loop | c05b96c7f-DklIyA-P.js, cc1aae219-DjR8LnUv.js | structured_content, ws(), user_denied, policy_denied, PERMISSIONS_ERROR, bs 330s garbage collection |
3.1 Message bubble kit and typography rules
Technical domain:
User & Assistant Message Bubbles
3.1.1 User message bubble ([data-cds="UserMessage"])
From frame-shell-chrome-CAw-tYzh.js and shared-2-Cb74JByF.js, user messages in CDS have a precise container geometry and state structure:
[data-cds="UserMessage"][data-author="self"][data-state="done"][data-text-size="md"]
├── AttachmentPreview (若随附附件,MessageAttachmentsFile 7.5rem 磁贴)
├── Bubble Container (yY: rounded-[--cds-message-bubble-radius] px-[--cds-message-bubble-pad-x] py-[--cds-message-bubble-pad-y])
│ ├── .cds-user-message-body (正文,若超出 300px 施加 3rem 底部渐变蒙版)
│ └── Show more / Show less (折叠切换按钮)
└── MessageActions (悬浮工具条: Copy, Edit, Retry, 快捷漫游)1. Actual radius and geometry formula (REA evidence)
/* frame-shell-chrome-CAw-tYzh.js 官方声明 */
.cds-root [data-cds=UserMessage],
.cds-root [data-cds=AssistantMessage][data-variant=bubble],
.cds-root [data-cds=AssistantMessage][data-cds-bubble-stack] {
--cds-message-bubble-radius: calc(var(--cds-radius) + calc(.25rem * var(--cds-rem-scale, 1)));
--cds-message-bubble-pad-x: var(--cds-pad-lg); /* 16px */
--cds-message-bubble-pad-y: var(--cds-pad-md); /* 12px */
--cds-message-bubble-measure: 85%;
}- Radius calculation: with the default
--cds-radius: 8pxand--cds-rem-scale: 1,calc(8px + 4px) = 12px; in the larger desktop mode it extends to18px. - Column width:
max-w-[var(--cds-message-bubble-measure)]is constrained to85%. Below a screen width of40remit falls back toclamp(85%, 448px, 100%). - Background and text color:
- Default mode:
bg-[var(--cds-bg-user-message)](a neutral light gray in light mode,var(--cds-bg-neutral)). - Personal tint:
bg-[var(--cds-bg-user-message-personal)] text-[color:var(--cds-text-user-message-personal)]. It is#dde8f7in light mode and#193c5fin dark mode. - Hover reaction: in the
.feed-bubble-reacts:hoverstate it injects--cds-bg-user-message: var(--cds-bg-neutral-hover); --feed-bubble-mix: 16%;.
- Default mode:
2. Long-message collapse and reading gradient mask (Truncation & Mask)
In shared-2-Cb74JByF.js, when a user message is taller than collapseThreshold = 300px, it collapses automatically:
/* REA 取证:3rem 底部衰减蒙版 */
.cds-user-message-collapsed {
max-height: 300px;
overflow: hidden;
mask-image: linear-gradient(to bottom, black calc(100% - 3rem), transparent);
-webkit-mask-image: linear-gradient(to bottom, black calc(100% - 3rem), transparent);
}3.1.2 Assistant reply body ([data-cds="AssistantMessage"])
Unlike traditional chat apps that also put the AI's reply in a bordered bubble, Claude Desktop follows a "content is the interface, borderless transparent canvas" philosophy.
1. Text inset and the stop rule (Text Inset & Stop Law)
/* frame-shell-chrome-CAw-tYzh.js 官方声明 */
.cds-root [data-cds=AssistantMessage] {
--cds-assistant-message-text-inset: 4px;
--cds-assistant-message-text-stop: clamp(0px, (100% - 384px) * .175, 56px);
}
/* 当正文内部嵌套卡片、数据表格、代码块或 MCP 面板时,微缩进立即精确归零 */
.cds-root [data-cds=AssistantMessage] :is(
[data-cds=Card], [data-cds=Surface], [data-cds=Panel], [data-cds=Banner],
[data-cds=Aside], [data-cds=Accordion], [data-cds=PreviewCard], [data-cds=Table],
[data-cds=DataTable], [data-cds=AskUserQuestion], [data-cds=CommentCard],
[data-cds=ChartContainer], [data-cds=PointChartContainer], [data-cds=FileDiff],
[data-cds=UserMessage]
) {
--cds-assistant-message-text-inset: 0px;
--cds-assistant-message-text-stop: 0px;
}- Inset rule: body text keeps a very slight
4pxleft inset, optically aligned with the Claude ✦ mark above it. - Card reset rule: as soon as a structured card appears in the content flow (a code block, an Artifact preview, an MCP authorization panel), Inset and Stop reset to
0pxso the card fills the column container.
2. Vertical Rhythm
From shared-styles-Bl9zqrv-.css:
$$\text{chat-turn-gap} = \text{chat-body} \times \text{chat-leading} \times 0.75$$
$$\text{chat-item-gap} = \text{chat-body} \times \text{chat-leading} \times 0.50$$
At the md size (chat-body: 14px, chat-leading: 1.42857), the gap between message turns is exactly 15px and the gap between blocks is exactly 10px.
3.2 Thinking collapse and its timing state machine
Technical domain:
Thinking Accordion & Timeline Status Machine
When Claude streams Thinking (the extended thinking process), it builds a four-stage micro-interaction system across CSS and the JS runtime (source evidence from cc1aae219-DtZTxCKj.css and shared-2-Cb74JByF.js).
stateDiagram-v2
[*] --> Generating: 模型开始输出思考流
Generating --> Generating: 3点波浪跳跃 (1.9s infinite cubic-bezier(.4, 0, .2, 1))
Generating --> Generating: 展开互斥锁锁定 (isOpen=true, 禁止折叠防抖动)
Generating --> Settled: 思考流终结
Settled --> Collapsed: 用户点击折叠 (Grid 1fr -> 0fr, .2s)
Collapsed --> Expanded: 用户再次点击 (Grid 0fr -> 1fr)3.2.1 The quiet three-dot bouncing wave (.timeline-quiet-dots)
The full keyframes extracted from the official cc1aae219-DtZTxCKj.css:
.timeline-quiet-dots {
gap: 3.5px;
display: inline-flex;
position: relative;
top: .26em;
}
.timeline-quiet-dots > span {
background: var(--cds-clay, #d97757);
opacity: 0;
border-radius: 50%;
width: 3px;
height: 3px;
animation: 1.9s cubic-bezier(.4, 0, .2, 1) infinite timeline-quiet-dot;
}
.timeline-quiet-dots > span:nth-child(2) { animation-delay: .15s; }
.timeline-quiet-dots > span:nth-child(3) { animation-delay: .3s; }
@keyframes timeline-quiet-dot {
0%, 8% { opacity: 0; transform: translateY(2px); }
22% { opacity: 1; transform: translateY(-2.5px); }
36%, 70% { opacity: 1; transform: translateY(0); }
90%, 100%{ opacity: 0; transform: translateY(0); }
}- Three-stage displacement:
- $0% \sim 8%$: sinks $2\text{px}$ to prepare the bounce;
- $22%$: shoots up to the highest point, $-2.5\text{px}$, at $100%$ opacity;
- $36% \sim 70%$: falls back naturally to the $0\text{px}$ baseline and rests;
- $90% \sim 100%$: fades out to zero and waits for the next $1.9\text{s}$ cycle.
3.2.2 Smooth rise and fall of the status line (timeline-status-*)
When thinking finishes or the status changes, there is no abrupt show/hide; a rise/fall animation hands over smoothly:
- Enter:
.timeline-status-enter-quiet,.24s cubic-bezier(.3, .7, .4, 1)rising fromtranslateY(6px)to the baseline; - Exit:
.timeline-status-exit-quiet,.18s ease-outdropping bytranslateY(-4px)while fading out.
3.2.3 Jitter-free CSS Grid expansion (.timeline-expand)
To avoid the performance cost and viewport jumps of computing scrollHeight in JavaScript, CDS uses modern CSS Grid size transitions throughout:
.timeline-expand {
grid-template-rows: 1fr;
display: grid;
}
.timeline-expand > * {
align-self: start;
min-width: 0;
min-height: 0;
}
@keyframes timeline-expand {
0% { opacity: 0; grid-template-rows: 0fr; }
100% { opacity: 1; grid-template-rows: 1fr; }
}
.timeline-expand-enter {
animation: .2s cubic-bezier(.19, 1, .22, 1) timeline-expand;
}3.3 The rich-text composer and command input engine
Technical domain:
Chat Composer & Next-Gen Engine
In the Claude Desktop production build, the composer has evolved from a simple <textarea> into a next-generation rich-text system with an input-method composition guard, syntax-tree parsing for two trigger characters, and context pills.
3.3.1 Core production source evidence (shared-frame-BfjMqbuN.js, cb6930dae-BD0W5ZM9.js)
In shared-frame-BfjMqbuN.js (pos 118400-120000), we extracted Anthropic's key race-condition defense for WebKit browsers:
// shared-frame-BfjMqbuN.js 官方实现
function Lm() {
return typeof window > "u" ? !1 : /Apple Computer/.test(window.navigator.vendor);
}
function Rm(e) {
Fm = e; // 记录 compositionend 的精确 timeStamp
}
function zm(e) {
(e.key !== "Enter" || e.shiftKey || e.altKey || e.keyCode === 229 || e.isComposing === !0 || e.nativeEvent?.isComposing === !0) && (Fm = Pm);
}
function Bm(e) {
// 1. 常规合成守卫与 keyCode 229 拦截
if (e.isComposing || e.keyCode === 229) {
Fm = Pm;
return !0;
}
if (Im.has(e)) return !0;
// 2. WebKit 500ms 赛跑后卫:若在合成结束 500ms 内触发 Enter,判定为输入法确认虚假键击,予以强行拦截!
if (Lm() && Math.abs(e.timeStamp - Fm) < 500) {
Fm = Pm;
return e.metaKey || e.ctrlKey ? !1 : (Im.add(e), !0);
}
return !1;
}
// 捕获阶段拦截 beforeinput 插入换行
var Vm = new Set(["insertParagraph", "insertLineBreak"]);
var Hm = ["beforeinput", "keydown", "keyup", "compositionstart", "focusout"];
function Um(e, t, n) {
if (!Im.has(e)) return;
let r = e,
i = e => {
if (e !== r) {
for (let e of Hm) t.removeEventListener(e, i, { capture: !0 });
e.type === "beforeinput" && "inputType" in e && typeof e.inputType == "string" && Vm.has(e.inputType) && (e.preventDefault(), n());
}
};
for (let e of Hm) t.addEventListener(e, i, { capture: !0 });
}In cb6930dae-BD0W5ZM9.js (pos 32800-37000), we extracted the forward tokenizer for Slash commands and Context Mentions:
// cb6930dae-BD0W5ZM9.js 官方实现
var qo = /`([^`\n]+)`/g, // 内联代码段排除
Qo = /(?<=^|\s)@([^\s`]+)/g, // @ 提及捕获
$o = /[.,;:!?'"\u3001\u3002\uFF0C\uFF1B\uFF1A\uFF01\uFF1F\u2026]+$/, // 尾随标点剥离正则(涵盖 CJK 全角符号)
es = /\.[a-z][^/]*$/i, // 文件后缀匹配
rs = 1024; // 最大路径字符长度限制
function as(e, t, n) {
if (!e.includes("@")) return is;
let r = e.includes("](") ? [...e.matchAll(di)].map(e => [e.index, e.index + e[0].length]) : [],
i = 0,
a = [...t?.keys() ?? []],
o = [],
s = 0;
for (let t of e.matchAll(Qo)) {
// 路径合法性校验与 256 个文件引用熔断上限
if (++s > 256) break;
let c = tr(t[1])[0].replace($o, ""),
l = c.replaceAll("\\", "/"),
u = l.includes("/"),
d = fi(u ? l : `./${l}`, n);
if (d && es.test(d.path)) {
o.push({ index: t.index, text: `@${c}`, fileRef: u ? d : { ...d, path: d.path.slice(2) } });
}
}
return o;
}3.3.2 Strict CJK input-method composition guard (IME Composition Guard)
- A long-standing industry pain point: in macOS / iOS WebKit, when a user types Latin letters with a pinyin input method and presses Enter to pick a candidate, the browser fires
compositionendfirst and then immediately dispatches an ordinarykeydown (key='Enter', isComposing=false). Relying onisComposingalone sends the unfinished message by mistake! - Claude's two-stage protection:
- Stage one: intercept
isComposing || keyCode === 229; - Stage two:
Lm() && Math.abs(e.timeStamp - Fm) < 500(a time difference under $500\text{ms}$ is treated as a spurious trailing key from composition), andUm()registers abeforeinputlistener in thecapturephase to blockinsertParagraph/insertLineBreak.
- Stage one: intercept
3.3.3 Auto height and the ghost element fallback (Field Sizing)
- Modern browsers: declare
style={{ fieldSizing: "content" }}natively and let the rendering engine compute the height; - Fallback: where that is unsupported, an isomorphic hidden mirror layer is used:
<div class="cds-text-entry-floored pointer-events-none invisible min-w-px overflow-y-auto font-sans break-words whitespace-pre-wrap select-none [@supports_(field-sizing:content)]:hidden"> {value}​ </div> - Height range: minimum
44px, maximumNX = "max-h-[min(40svh,24rem)]"($384\text{px}$ or $40\text{svh}$).
3.4 Model switcher and token usage gauge
Technical domain:
Model Switcher & Context Gauge
3.4.1 The three official model tiers and the adaptive thinking architecture (c30db9bec-DjF3pT_h.js)
In the current client, Anthropic has completed the generational move of the thinking architecture from fixed to adaptive:
- Adaptive thinking upgrade:
"Using Claude with model and 'thinking.type=enabled' is deprecated. Use 'thinking.type=adaptive' instead which results in better model performance in our testing."
- Main model lineup:
- Claude 3.7 Sonnet (
claude-3-7-sonnet-latest): the flagship model, with native adaptive thinking (adaptive thinking) andeffortlevels (low,medium,high,max); - Claude 3.5 Haiku (
claude-3-5-haiku-latest): very fast responses and high-throughput streaming output; - Claude 3 Opus (
claude-3-opus-latest): deep mathematical analysis and large-scale reasoning.
- Claude 3.7 Sonnet (
3.4.2 Prompt Caching cost breakdown (c058e28e3-DXZbxFF9.js)
From c058e28e3-DXZbxFF9.js, Anthropic defines strict lifetimes and a four-color metering model for context caching in production:
- Two hot-cache lifetimes:
ephemeral_5m_input_tokens: $5\text{min}$ ($300,000\text{ms}$) standard hot cache;ephemeral_1h_input_tokens: $1\text{h}$ ($3,600,000\text{ms}$) long-lived conversation cache.
- Four-color cost bar:
- Emerald:
cache_read_input_tokens(cache read hits, with a 90% discount, billed at only 0.1x); - Sky blue:
cache_creation_input_tokens(writes to the 5m/1h hot cache, billed at 1.25x); - Clay orange:
input_tokens(regular cold-start input); - Violet:
output_tokens(model generation).
- Emerald:
3.4.3 Ring SVG capacity gauge
The tiny capacity pill contains a lossless vector ring:
$$\text{Circumference} = 2 \times \pi \times r \approx 62.83 \quad (r=10)$$
$$\text{StrokeDashoffset} = \text{Circumference} \times (1 - \text{UsageRatio})$$
When usage exceeds 80% (total > maxWindow * 0.8), the gauge turns from neutral green to the clay warning color and starts a breathing pulse.
3.5 Interactive tool calls and MCP decision cards
Technical domain:
Tool Decisions & Human-in-the-Loop
For Model Context Protocol (MCP) and local command-line calls, Claude follows the security rule "operations with significant side effects require human approval; read-only operations are authorized smoothly" (source evidence from c05b96c7f-DklIyA-P.js and cc1aae219-DjR8LnUv.js).
3.5.1 Structured tool-call extraction and decision logic in production
Signature extraction and whitelist GC extracted from c05b96c7f-DklIyA-P.js:
// c05b96c7f-DklIyA-P.js 官方实现
function ws(e, t, n) {
let r = [];
for (let i of e) {
if (i.type !== "tool_use" || !i.name || !Te(i, ["write"], n, i.name) || !i.id) continue;
let e = t.get(i.id);
if (!e || e.is_error) continue;
let a = e.structured_content;
r.push({ toolUseId: i.id, path: a?.path ?? "", version: a?.version ?? "" });
}
return r;
}
// 白名单 330 秒超时自动回收
function Ds(e) {
for (let [t, n] of bs) e - n.clientAckedAtMs > 33e4 && bs.delete(t);
for (let e of bs.keys()) {
if (bs.size < Ss) break;
bs.delete(e);
}
}3.5.2 Strict three-state authorization lifecycle
pending_approval(awaiting approval): the card shows a warm, translucent clay glow border (border-[var(--cds-border-accent)]/50) and displays the tool's input JSON tree;running(executing): after approval the card enters the running state, the indicator becomes a spinning blue ring, and downstream console output is captured;success/error(terminal state): when execution finishes the badge switches, and the console output can be expanded.
3.5.3 Actual settle reasons (Settle Reasons)
user_denied: the user clicked Deny; the model receives:{ is_error: true, error_text: "User denied tool execution" }, prompting it to reconsider and propose an alternative;policy_denied: blocked by an enterprise or sandbox security policy;PERMISSIONS_ERROR: the required local file system read/write permission is missing.
3.5.4 The three decision buttons
- [Allow Once]: the primary button in brand clay (
var(--cds-fill-brand)), granting permission for this single call only; - [Always Allow]: writes the tool signature into a temporary conversation cache ($330\text{s}$ garbage collection), so later calls are not interrupted;
- [Deny]: a ghost danger-red button that blocks the side effect immediately.
3.6 Production component library overview
Every interaction spec in this chapter is implemented in code-examples/ as a production TypeScript component with zero errors:
| Component | Source file | Production specs and mechanisms covered |
|---|---|---|
ChatMessageBubbles | code-examples/ChatMessageBubbles.tsx | data-cds="UserMessage", data-cds="AssistantMessage", the 18px bubble radius formula, 300px truncation gradient mask, the Text Inset 4px stop rule |
ThinkingAccordion | code-examples/ThinkingAccordion.tsx | .timeline-quiet-dots 1.9s three-dot wave, jitter-free CSS Grid 0fr expansion, expansion lock while streaming |
RichComposerWithMentions | code-examples/RichComposerWithMentions.tsx | WebKit 500ms post-composition delta guard, CJK full-width punctuation stripping, 256-file reference circuit breaker, field-sizing: content ghost fallback |
ModelSwitcherWithTokenGauge | code-examples/ModelSwitcherWithTokenGauge.tsx | Claude 3.7 Sonnet / Haiku / Opus lineup, adaptive thinking and effort levels, 5m/1h Prompt Caching four-color breakdown, ring SVG gauge |
McpToolDecisionCard | code-examples/McpToolDecisionCard.tsx | Three-state authorization lifecycle, user_denied / policy_denied / PERMISSIONS_ERROR settle reasons, input JSON review and the 330s whitelist |
cds-theme.css | code-examples/cds-theme.css | --cds-message-bubble-*, --cds-assistant-message-*, .timeline-*, @keyframes timeline-*, .feed-bubble-reacts |
⬅️ Chapter 2: Atomic Components & Interactive Controls · 📚 1.x overview · Chapter 4: Motion Physics, Shaders & Ambient Light ➡️ · Live React chat UI demos
Open Claude UI is an unofficial community analysis, MIT licensed. Claude is a trademark of Anthropic; this project is not affiliated with Anthropic.
02 · Atomic Components & Interactive Controls
Claude UI components spec: 5-step button height scale, 6 button variants, segmented control, 6px status dots, dialog sizes and 7.5rem attachment tiles.
04 · Motion Physics, Shaders & Ambient Light
Claude UI animation spec: full @keyframes set, 4 Framer Motion spring presets, reasoning glow at 1.8s/2.2s, shimmer curve and the 7-stop scroll fade mask.