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
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 passesdefaultOpen. - There is no step or item export.
Plangives you a header, a description, a content region and a footer; the steps inside it are whatever you render —Taskfrom 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
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
| Patch | What changed, and why |
|---|---|
button-floor | primary — 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-floor | primary — queue.tsx — QueueSectionTrigger. Measured 1222x36. A full-width section header that collapses its list. |
suggestion-dense | dense — 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.