Suggestions and plans

What the model offers to do next, and what it says it is going to do. Suggestion is the chip row above a composer, Plan is the card it writes its intent into, Queue is the list of work waiting, and Checkpoint is a marker you can return to.

Import

import { Suggestion, Suggestions } from '@elirobinson/ai-elements/components/suggestion';
import {
  Plan,
  PlanContent,
  PlanDescription,
  PlanHeader,
  PlanTitle,
  PlanTrigger,
} from '@elirobinson/ai-elements/components/plan';
import {
  Queue,
  QueueItem,
  QueueList,
  QueueSection,
  QueueSectionContent,
  QueueSectionLabel,
  QueueSectionTrigger,
} from '@elirobinson/ai-elements/components/queue';
import { Checkpoint, CheckpointTrigger } from '@elirobinson/ai-elements/components/checkpoint';

Suggestions

Suggestion calls onClick(suggestion) with the string, not with the event — it is built to be handed straight to sendMessage. It is a real <button type="button">, classified dense at 32px because it is a chip and a chip that is a control is dense everywhere else in this system.

Suggestions wraps the row in a horizontal scroll area whose scrollbar is hidden (<ScrollBar className="hidden">), so a row wider than its container scrolls with no visible affordance saying so.

Plan

Ship the audit
Measure, classify, patch, record.

Two things about Plan that its name does not tell you:

  • It is a card wrapped in a collapsible with no defaultOpen, so it starts closed. The demo above passes defaultOpen.
  • There is no step or item export. Plan gives you a header, a description, a content region and a footer; the steps inside it are whatever you render — Task from Tools and tasks is the shape it is drawn for.

PlanTitle and PlanDescription swap themselves for a Shimmer while isStreaming, which is the same unguarded animation described on Reasoning. PlanTrigger is an icon button carrying an sr-only "Toggle plan".

Queue and checkpoint

  • Run the migration

QueueList renders a <ul> and QueueItem an <li>, so the queue is a list to a screen reader and not just visually. QueueSectionTrigger is a real <button> floored to var(--target) — see below.

QueueItemAction is opacity-0 until its row is hovered, with no focus-visible reveal. A keyboard user can tab to an action they cannot see. It is the same pattern as AttachmentRemove on Prompt input, and it is worth fixing at the call site with a group-focus-within:opacity-100 until it is fixed upstream.

Checkpoint is a row with a trailing separator; CheckpointTrigger is a Button, and passing tooltip wraps it in a tooltip rather than changing the control.

What this system changed

PatchWhat changed, and why
button-floorprimary — ui/button.tsx — every <Button> in the tree. Measured default 36px, sm 32px, icon 36x36, icon-sm 32x32, icon-lg 40x40. The floor every vendored button gets unless it is classified dense elsewhere in this list. shadcn sizes at 32-40px, which clears AA and misses this system’s AAA default; the `not-data-[touch-target=dense]` guard is what keeps that a default rather than a blanket, and it cannot be undone by a consumer’s className the way a merged utility could.
queue-section-trigger-floorprimary — queue.tsx — QueueSectionTrigger. Measured 1222x36. A full-width section header that collapses its list.
suggestion-densedense — suggestion.tsx — Suggestion. Measured 32px tall, clears 24x24. A 32px rounded-full pill is MUI’s Chip exactly, and contracts.json already settled that a chip which is a control is dense at that scale (#114). Applying a different answer to the same shape here would be the inconsistency, not the exemption.

Read from vendoredElementTargets in @elirobinson/ai-patterns/contracts, which the browser audit writes.