Skip to content
KONIGI Patterns

AI Assistants / Understanding answers / Reasoning disclosure

‹ 3 of 5 ›

Reasoning disclosure

People waiting for an answer need enough information to spot a misunderstood request.

Updated September 12, 2026

Problem

During a long wait, people cannot tell whether the assistant understood their request or is working on the wrong problem.

Solution

Show a brief summary of the assistant’s approach separately from the answer. Keep extra detail collapsed and show progress or elapsed time while work continues.

Make the summary specific enough to reveal a misunderstood request. Keep Stop available so people can interrupt and correct it.

Use a concise summary intended for display. Explain that it is a summary of the approach, not a complete record of the model’s internal computation or proof that the answer is correct.

Use when

A task takes long enough to need progress information, and a summary can help people spot a misunderstanding.

Don’t use when

The answer arrives in two seconds, or the task has no interesting intermediate state. A disclosure panel over a lookup manufactures the appearance of deliberation and slows the product’s felt speed for nothing.

Trade-offs

Extra explanation can distract people from checking the answer itself. A plausible summary can also create confidence without establishing accuracy. Long descriptions add reading work, so the collapsed view needs to be useful on its own.

Checklist

  • Is it collapsed by default?
  • Does the collapsed line carry enough for the viewer to catch a misread question?
  • Is a duration shown, and is it the real one?
  • Does the panel claim to explain why, or to show working?
  • Can the viewer stop generation from inside the reasoning phase?
  • Does reasoning count toward usage limits, and is that visible?
  • Is the reasoning included when the answer is copied, shared, or exported?
  • Does a screen reader get the summary without the whole trace?
  • What happens when reasoning is longer than the answer?
  • Is raw reasoning ever shown, and has anyone read a hundred samples of it?

Compare

Claude streams a summarised version of its reasoning by default and keeps the full trace behind an API setting. The panel is calibration for a reader, not a log for a developer. ChatGPT shows a running progress narration during longer reasoning, so the disclosure doubles as the progress indicator for the pre-answer wait. Gemini presents its reasoning as a structured plan more than a monologue, which is easier to skim and further from what the model actually did. Perplexity mostly spends this space on the search steps instead. Showing what it looked for is more verifiable than showing what it concluded.

Streaming response presents the answer as it arrives. Tool call trace records actions and lookups. Citation chip links claims to sources.

Stop generation lets people interrupt the work. Response collapse hides detail within the answer itself.

Reasoning disclosure anatomy A collapsed reasoning panel above an answer, carrying a one-line summary and a duration, expanded below to show a summarised trace rather than a raw chain, with the answer running shorter than the reasoning that produced it. Collapsed, summarised, and honest about what it is Working: restating the question, checking the two dates 38s summarised, not the raw chain 200 words of answer under 800 words of working 1 2 3 4 1 CATCH THE MISREAD Seeing the question restated wrongly at second three is the whole value. 2 A REAL DURATION A number next to the label does more for trust than any adjective. 3 WORKING, NOT WHY This is a narration produced alongside the answer, not a trace of the computation. 4 LONGER THAN THE ANSWER Reading 800 words to reach 200 is a worse product, not a more transparent one. Once reasoning is visible, people tune their prompts against the narration instead of the output.
Wireframe — the pattern's anatomy, not any one product's version of it

Implementation

A collapsed panel carrying a one-line summary and a duration. What expands is a summarised trace rather than the raw chain, because 200 words of answer buried under 800 words of working reads worse than the answer alone.

shadcn
npx shadcn@latest add collapsible button
npm
lucide-react
Tokens
--card--card-foreground--muted-foreground--border
38s

The question is whether the tenant's break date comes before the rent review, and what notice that needs.

The lease starts 1 March 2024. The break is at the end of year two, so 28 February 2026, on six months' written notice. The review is at the third anniversary, 1 March 2027.

The break falls a year before the review, so notice has to be served by 31 August 2025, with rent paid up and vacant possession on the day.

summarised, not the raw chain

Yes. The break on 28 February 2026 comes a year before the review on 1 March 2027. Notice has to be served by 31 August 2025, with rent paid up and the premises returned empty.

ReasoningDisclosure.tsxCollapsed by default, labelled as working rather than thinking, a real duration in the header, stop while it streams.

import { useState } from "react";
import { ChevronRight, Square } from "lucide-react";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

type Props = {
  /** One line the viewer can read without expanding: enough to catch a
   *  misread question at second three. The panel prefixes "Working:", never
   *  "Thinking:"; this is narration produced alongside the answer, not a
   *  trace of the computation. */
  summary: string;
  /** The summarised trace, a paragraph per step. Never the raw chain. */
  trace: string[];
  /** The real one. A duration next to the label does more for trust than any adjective. */
  durationMs: number;
  /** True while the working is still arriving. Shows stop, because the viewer
   *  who has spotted a misread should not have to wait for the answer. */
  streaming?: boolean;
  onStop?: () => void;
  /** Collapsed by default. The working runs longer than the answer and matters less. */
  defaultOpen?: boolean;
};

const duration = (ms: number) => {
  const s = Math.round(ms / 1000);
  return s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`;
};

export function ReasoningDisclosure({ summary, trace, durationMs, streaming = false, onStop, defaultOpen = false }: Props) {
  const [open, setOpen] = useState(defaultOpen);
  return (
    <Collapsible open={open} onOpenChange={setOpen}>
      <div className="flex items-center gap-2.5 rounded-lg border px-3 py-2">
        <CollapsibleTrigger className="flex min-w-0 flex-1 items-center gap-2.5 text-left" aria-label={`working: ${summary}`}>
          <ChevronRight className={cn("size-3 shrink-0 text-muted-foreground transition-transform", open && "rotate-90")} aria-hidden />
          <span className="truncate text-xs text-card-foreground">Working: {summary}</span>
        </CollapsibleTrigger>
        {streaming && onStop ? (
          <Button variant="ghost" size="sm" className="h-6 px-1.5 text-[11px]" onClick={onStop}>
            <Square className="size-2.5 fill-current" aria-hidden /> stop
          </Button>
        ) : (
          <span className="text-[11px] tabular-nums text-muted-foreground">{duration(durationMs)}</span>
        )}
      </div>
      <CollapsibleContent>
        <div className="mt-3 rounded-lg border border-dashed p-3">
          {trace.map((step, i) => (
            <p key={i} className="text-xs leading-relaxed text-muted-foreground [&+p]:mt-2">{step}</p>
          ))}
          {/* Say what it is. The full chain belongs behind a developer affordance. */}
          <p className="mt-3 text-right text-[11px] text-muted-foreground">summarised, not the raw chain</p>
        </div>
      </CollapsibleContent>
    </Collapsible>
  );
}

demo.tsxHow it is called: the summary line, the summarised trace, the duration, opened so the trace shows.

import { ReasoningDisclosure } from "./ReasoningDisclosure";

/**
 * The working for one lease question, opened so the summarised trace shows.
 * A product would leave `defaultOpen` off and let the viewer expand it.
 */
const TRACE = [
  "The question is whether the tenant's break date comes before the rent review, and what notice that needs.",
  "The lease starts 1 March 2024. The break is at the end of year two, so 28 February 2026, on six months' written notice. The review is at the third anniversary, 1 March 2027.",
  "The break falls a year before the review, so notice has to be served by 31 August 2025, with rent paid up and vacant possession on the day.",
];

export default function Demo() {
  return (
    <div className="rounded-lg border bg-card p-4">
      <ReasoningDisclosure
        summary="restating the question, checking the two dates"
        trace={TRACE}
        durationMs={38_000}
        onStop={() => console.log("stop")}
        defaultOpen
      />
      <p className="mt-4 border-t pt-4 text-sm leading-relaxed text-card-foreground">
        Yes. The break on 28 February 2026 comes a year before the review on 1 March 2027. Notice has to be served by 31 August 2025, with rent paid up and the premises returned empty.
      </p>
    </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.