Skip to content
KONIGI Patterns

AI Assistants / Messages and responses / Message turn

‹ 2 of 6 ›

Message turn

Readers need to distinguish their messages from the assistant’s answers.

Updated September 12, 2026

Problem

People scanning a conversation need to distinguish their questions from the assistant’s answers without rereading every message.

Solution

Give user messages a compact, distinct treatment. Let assistant responses use enough width for readable prose, tables, and code.

Keep questions easy to spot so they act as landmarks in a long conversation. Collapse long pasted input behind a clear expansion control when it would otherwise dominate the page.

Identify each speaker for screen readers as well as visually. Do not rely on color or alignment alone. Show timestamps or avatars only where they help people understand the conversation.

Use when

A person and an assistant exchange messages that remain visible in a conversation.

Don’t use when

The exchange is one question and one answer with no history, which is how answer engines and most in-product assists work. There turn chrome is pure decoration, and the answer reads better as a document with the question as its heading.

Trade-offs

Chat bubbles can leave too little room for long answers. Removing all visual differences makes speakers harder to distinguish. Collapsing long messages saves space but needs a clear signal that some text is hidden.

Checklist

  • Can the reader find their own question from six turns ago without reading any answers?
  • Does the assistant turn run at the full measure, or is it boxed?
  • What does a two-thousand-word pasted user turn do to the layout?
  • Is the speaker exposed programmatically, or only through colour and position?
  • Does the attribution survive right-to-left text?
  • What does the avatar or the timestamp earn on every single turn?
  • How does a code block inside an assistant turn behave at the narrowest supported width?
  • Is there banding down the page once turns get short?
  • Where does a system message or an error sit in the two-voice scheme?
  • Can a single turn be linked to, quoted, or shared on its own?

Compare

ChatGPT tints and constrains the user turn and lets the assistant turn run full width with no avatar. That’s the asymmetry in its most common form. Claude does the same and pushes it further by moving long generated documents out of the transcript entirely, so the turn stays a turn. Perplexity drops turn chrome almost completely: the question becomes a heading and the answer is a document underneath it, which is the right call for one-shot use and loses the thread once a conversation actually develops. Slack takes the opposite trade, because an AI reply lands in the existing message list and inherits the same avatar, name and timestamp row every human message gets. The assistant becomes a participant rather than a surface.

Streaming response fills the assistant’s message. Response actions provide controls for that answer. Conversation history organizes complete conversations.

Edit and resend revises a user message. Composer is where new messages are written.

Message turn anatomy Two turns in one column. The user turn is a compact tinted container acting as a landmark; the assistant turn runs at the full measure with no container and no avatar. A long pasted user turn is collapsed to a few lines with an expander. Asymmetry, because the two turns are different sizes Summarise the attached lease Show all 247 words no avatar, no timestamp 1 2 3 4 1 THE USER TURN Compact and tinted. It is the label on the answer, and the landmark people scan. 2 THE ASSISTANT TURN Full measure, no container. It is the content, so nothing decorates it. 3 WHAT WAS REMOVED An avatar is a gutter repeated forever. Both turns happened four seconds apart. 4 THE LONG PASTE Two thousand pasted words make the landmark the biggest thing on the page. Colour and position carry none of this to a screen reader. The speaker has to be in the markup.
Wireframe — the pattern's anatomy, not any one product's version of it

Implementation

Two turns, deliberately asymmetric. The user turn is a compact tinted container acting as a landmark; the assistant turn runs at full measure with no container and no avatar, because it is the thing being read.

shadcn
npx shadcn@latest add button
Tokens
--foreground--card--muted--border--chart-1
  1. You: Summarise the attached lease
  2. Assistant: The lease runs five years from 1 March 2024 at £42,000 a year, reviewed upward only at the start of year three. There is a tenant-only break at the end of year two on six months' written notice, conditional on the rent being paid up to date and vacant possession being given. The deposit is three months' rent, held in a separate account and returned within 30 days of the end of the term, less any agreed dilapidations.
  3. You: Also check this against what you said. 12. BREAK CLAUSE 12.1 The Tenant may determine this Lease on the Break Date by giving the Landlord not less than six months' prior written notice. 12.2 The Lease will determine on the Break Date only if the Tenant has paid all Annual Rent due up to and including the Break Date, and gives vacant possession of the Premises on the Break Date. 12.3 If the Lease determines under this clause the Landlord shall refund to the Tenant, within 14 days, any Annual Rent paid in advance for the period after the Break Date. 13. RENT REVIEW 13.1 The Annual Rent shall be reviewed on the Review Date to the greater of the Annual Rent payable immediately before the Review Date and the Open Market Rent. 13.2 The Open Market Rent shall be agreed between the parties or, in default of agreement within two months before the Review Date, determined by an independent surveyor acting as an expert. 14. DEPOSIT 14.1 The Tenant shall on the date of this Lease pay to the Landlord the Deposit, being a sum equal to three months' Annual Rent, to be held in a separate interest-bearing account. 14.2 The Landlord may draw on the Deposit to make good any breach of the Tenant's covenants. 14.3 The Landlord shall return the balance of the Deposit to the Tenant within 30 days after the end of the Term, less any sums properly deducted under clause 14.2.

MessageTurn.tsxA list of turns, each with its speaker in the markup. A user turn past 120 words collapses to two lines.

import { useState } from "react";
import { Button } from "@/components/ui/button";

export type Turn = { role: "user" | "assistant"; text: string };

/** A user turn past this many words is a paste, and a paste collapses. Two
 *  thousand pasted words otherwise make the landmark the biggest thing on
 *  the page. */
const COLLAPSE_OVER = 120;

/**
 * The transcript is a list and every turn is an item with a speaker in the
 * markup. Tint and alignment carry none of this to a screen reader, and
 * alignment inverts under right-to-left text.
 */
export function Transcript({ turns }: { turns: Turn[] }) {
  return (
    <ol className="flex flex-col gap-4">
      {turns.map((t, i) => (
        <li key={i}><MessageTurn {...t} /></li>
      ))}
    </ol>
  );
}

export function MessageTurn({ role, text }: Turn) {
  const [open, setOpen] = useState(false);

  // The assistant turn is the content, so nothing decorates it: no container,
  // no avatar, no timestamp, the full measure. In practice this is rendered
  // markdown; the point is what wraps it, which is nothing.
  if (role === "assistant") {
    return (
      <div className="text-sm leading-relaxed text-foreground">
        <span className="sr-only">Assistant: </span>
        {text}
      </div>
    );
  }

  const words = text.trim().split(/\s+/).length;
  const collapsed = words > COLLAPSE_OVER && !open;

  // The user turn is the label on the answer and the landmark people scan
  // for, so it is compact, tinted and narrow.
  return (
    <div className="flex flex-col items-end gap-1.5">
      <div className="max-w-md rounded-xl bg-muted px-3.5 py-2 text-sm text-foreground">
        <span className="sr-only">You: </span>
        <span className={collapsed ? "line-clamp-2" : undefined}>{text}</span>
      </div>
      {collapsed && (
        <Button variant="link" size="sm" className="h-auto p-0 text-xs text-chart-1" onClick={() => setOpen(true)}>
          Show all {words.toLocaleString()} words
        </Button>
      )}
    </div>
  );
}

demo.tsxHow it is called: three turns, the third a paste long enough to collapse.

import { Transcript } from "./MessageTurn";

const PASTE =
  "Also check this against what you said. 12. BREAK CLAUSE 12.1 The Tenant may determine this Lease on the Break Date by giving the Landlord not less than six months' prior written notice. 12.2 The Lease will determine on the Break Date only if the Tenant has paid all Annual Rent due up to and including the Break Date, and gives vacant possession of the Premises on the Break Date. 12.3 If the Lease determines under this clause the Landlord shall refund to the Tenant, within 14 days, any Annual Rent paid in advance for the period after the Break Date. 13. RENT REVIEW 13.1 The Annual Rent shall be reviewed on the Review Date to the greater of the Annual Rent payable immediately before the Review Date and the Open Market Rent. 13.2 The Open Market Rent shall be agreed between the parties or, in default of agreement within two months before the Review Date, determined by an independent surveyor acting as an expert. 14. DEPOSIT 14.1 The Tenant shall on the date of this Lease pay to the Landlord the Deposit, being a sum equal to three months' Annual Rent, to be held in a separate interest-bearing account. 14.2 The Landlord may draw on the Deposit to make good any breach of the Tenant's covenants. 14.3 The Landlord shall return the balance of the Deposit to the Tenant within 30 days after the end of the Term, less any sums properly deducted under clause 14.2.";

/** Three turns: a question, the answer at full measure, and a paste that collapses. */
export default function Demo() {
  return (
    <div className="rounded-lg border bg-card p-4">
      <Transcript
        turns={[
          { role: "user", text: "Summarise the attached lease" },
          {
            role: "assistant",
            text: "The lease runs five years from 1 March 2024 at £42,000 a year, reviewed upward only at the start of year three. There is a tenant-only break at the end of year two on six months' written notice, conditional on the rent being paid up to date and vacant possession being given. The deposit is three months' rent, held in a separate account and returned within 30 days of the end of the term, less any agreed dilapidations.",
          },
          { role: "user", text: PASTE },
        ]}
      />
    </div>
  );
}
What it renders. Identical markup in both panes, with only the token values changing.

Examples

No screenshots yet for this pattern. Browse Products to see other examples.