AI thinking card

Shows an assistant working, then the answer it arrives at.

Thinking…
Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/ai-thinking-card.json
Usage
Give it a set of states while the work runs, then the answer when it lands.
TSImport
import { AiThinkingCard } from "@/components/blocks/ai-thinking-card";
TSExample
import { AiThinkingCard } from "@/components/blocks/ai-thinking-card";

export function AssistantReply({ answer }: { answer?: string }) {
  return (
    <AiThinkingCard
      title="Squad AI"
      states={["Thinking", "Reading the brief", "Drafting a reply"]}
      answer={answer}
    />
  );
}
Composition
A card that tweens its own height, a rotating status line, and the answer one word at a time.
AiThinkingCard              (card-surface + t-resize — it tweens its own height)
├── .t-stagger × 3          (mark, title, toggle — each reveals on its own mount)
├── .t-icon-swap            (one grid cell, two children cross-fading)
│   ├── LoadingState        (the syncing pill's loader — pattern="drive", sm)
│   └── icon                (Phosphor, duotone — what the dots become)
├── title | .t-think        (the state while it thinks, the title once it answers)
│   └── .t-think            (rotating status line)
│       ├── .t-think-sizer  (hidden, holds the longest state — fixes the width)
│       └── .t-think-text   (the visible line; shimmer on ::before)
├── aside                   (right of the heading line)
├── collapse toggle         (Button + caret — `collapsible`, once there is content)
├── chip                    (`[label](chip:type/id)` → hover card, via `resolveChip`)
├── .t-stream-w × n         (the answer — one span per word, or per block with `markdown`)
└── footer                  (anything under the answer)
Thinking, then the answer
The whole arc, with no controls: it starts thinking on mount and the answer takes its place when the model comes back — pass `answer` and the card does the rest. The status line rotates through `states` every `stateDuration` (2s by default), each one shimmering while it holds, and a hidden sizer carries the longest state so the box never resizes mid-swap. Collapsible, as it is by default — and the toggle only shows up once there is something to collapse. The mark, the title and that toggle each reveal on their own mount with the texts-reveal stagger, which is deliberately a different motion from the answer's word-by-word stream.
Thinking…
Answering
Passing `answer` ends the thinking: the dots cross-fade into `icon` rather than vanishing. The card measures both heights and tweens between them — `height: auto` cannot tween — and the words resolve in `--stream-gap` apart. Press Ask again to watch it twice.
Thinking…
Markdown
`markdown` renders the answer as markdown — headings, lists, emphasis, quotes, code. The reveal then resolves a block at a time rather than a word at a time, each block delayed by how much text precedes it, so the document still assembles at reading pace. A word span cannot straddle markdown structure, which is why the step is one grain coarser here.
Squad AI

Three pieces left

Your fall series still needs:

  1. Stage graphic — the long pole, so start here
  2. Sermon slides — let them follow the stage type
  3. Social cutdowns — last, once the look has settled

The stage graphic sets the type treatment everything else inherits.

Set the kit with npm run kit fall.

Chips
The rule an agent writes to: a plain markdown link with a `chip:` scheme — `[Josh Sorenson](chip:person/u_8123)`, `[Fri 24 Sept](chip:date/2026-09-24)`, `[Fall Series](chip:project/p_442)`. `resolveChip` turns the reference into what its popover shows and `chipIcon` gives it a glyph; return nothing and it renders as the plain words the agent wrote, so an agent naming something you have no data for costs nothing. The type before the slash is yours to define — the card never interprets it.
Squad AI

moved the stage graphic to , which puts it after locks its type.

Everything else on that project still lands in the same week.

With chrome
`icon`, `title`, `aside`, `footer` and `tone` are all props. The tint reaches the dots; the footer sits under the answer and grows the card with it.
Squad AI
Opus 5

Your fall series has three pieces left: the stage graphic, the sermon slide set, and the social cutdowns. The stage graphic is the long pole, so I would start there and let the slides follow its type treatment.

Collapsed
`collapsible` is on by default. Collapse and expand ride the same height tween as the answer arriving, so the card is only ever doing one kind of motion.
Squad AI
API Reference
Props for the AiThinkingCard block.

AiThinkingCard

PropTypeDefaultDescription
statesreadonly string[]a generic setStatus lines to rotate through while thinking. Each one trails off with an ellipsis unless it already ends in punctuation, so pass them clean. The longest sets the width of the box, so the line never resizes as it swaps.
stateDurationnumber2000How long each state holds before the next swaps in, ms. The swap itself rides `--think-swap` / `--think-gap` on top of it.
iconReactNodea duotone SparkleThe icon the dots become when the thinking ends — a Phosphor icon at `weight="duotone"`, the SDK's icon voice. The two share one grid cell and cross-fade, so the loader does not vanish and leave the icon to appear.
titleReactNode—Heading for the card. It waits for the answer — while the card is thinking the status line IS the line, and the title takes its place when the state goes.
asideReactNode—Right of the heading — a model name, a stop button, an elapsed timer. The leading slot is icon-sized, so anything that is not an icon belongs here.
answerstring—The answer. Passing it ends the thinking: the card grows to fit and the words resolve in. Changing it streams the new one.
thinkingboolean`answer` is emptyForce the thinking state on or off, for work you are tracking yourself.
wordDelaynumber--stream-gap (60)Gap between streamed words, ms.
markdownbooleanfalseRender `answer` as markdown. The reveal then cascades a block at a time instead of a word at a time, on the same `wordDelay` clock.
resolveChip(ref: ChipRef) => ReactNode—Resolve a `[label](chip:type/id)` reference into what its popover shows. `ChipRef` is `{ type, id, label }`. Return nothing and the reference renders as plain text. Needs `markdown`.
chipIcon(ref: ChipRef) => ReactNode—Optional glyph inside the chip, ahead of its label.
toneLoadingState tone"muted"Brand tint for the dots — brand, violet, coral, amber, magenta, plum, muted.
size"sm" | "default" | "lg""sm"Dot scale. `sm` is the syncing pill's, which this loader borrows so the two read as one product.
collapsiblebooleantrueGive the card a collapse toggle. It only appears once there is something to collapse — an `answer` or a `footer` — so a card that is still thinking has no control on it. Collapsing rides the same height tween as the answer arriving.
defaultCollapsedbooleanfalseWhere an uncontrolled card starts.
collapsedboolean—Controlled collapse state. Pair with `onCollapsedChange`.
onCollapsedChange(collapsed: boolean) => void—Fires whenever the card collapses or expands.
collapseLabelstring"Toggle details"Accessible name for the toggle.
footerReactNode—Anything below the answer — sources, actions, a follow-up field.