Skip to content
ComponentsDomain cardsPlanCard

PlanCard

Hero example

When to use

Reach for PlanCard when a tier_2 or tier_3 agent has decomposed a user request into ordered steps and the user is watching progress unfold ("File Delaware franchise tax: compute → generate AR → submit → archive"). The card is the canonical surface for agentic plan execution — each step represents a discrete operation, and completed advances as the agent finishes each one. Render the card inline in the composer reply; the wrapping agent thread shows live updates.

Don't reach for PlanCard when the items are independent (not ordered) — use a checklist inside FilingStatusCard for unordered compliance sub-items, or a generic list. Don't use it for finished plans either; an archived plan reads better as a Timeline of the events that happened.

Do — render PlanCard when an agent says "Here's my plan: 1) draft the consent, 2) collect signatures, 3) file the certificate."
Don't — render PlanCard for an unordered TODO list. The ordinal sequence is part of the contract.

Anatomy

Two structural regions:

  1. Header. List icon + title (defaults to "Plan") + right-aligned {completed}/{total} progress in mono.
  2. Ordered list (<ol>). One row per item. Each row has:
    • 16px circular indicator. Done items: filled --status-green with white . Active item (i === completed): empty circle with a 5px filled inner dot in --fg. Pending items: empty circle with 1.4px --ink-18 border.
    • Item label. Done items have line-through decoration in --ink-28. Active and pending items render in --fg and --fg-muted respectively.

The <ol> carries no visible numbering — the indicator + ordering of children supplies it. Screen readers announce position via the ordered-list semantics.

Props

PropTypeDefaultDescription
titlestring
itemsrequiredstring[]
completednumber0

The completed prop is a 0-indexed cursor — completed: 0 means "no steps done yet, first step is active." completed: items.length means "all steps done, no active step."

States

The card surfaces three per-item states via the completed cursor:

StateTriggerIndicator
Donei < completedFilled green circle with
Activei === completedEmpty circle with filled inner dot
Pendingi > completedEmpty circle with --ink-18 border

The card-level "complete" state (every item done) is implicit when completed === items.length. Consumer is responsible for transitioning the card away (or rendering a follow-up surface) when this happens.

Density

In compact, item vertical padding drops from 7px to 4px. Indicator and font sizes stay constant.

Themes

Tokens consumed

colors17 tokensv2.3.0
#3FBF6E
matter
src ↗
#1b7a47
matter
src ↗
#E8A845
matter
src ↗
#8a5e1c
matter
src ↗
#8C8C8C
matter
src ↗
#5A5A5A
matter
src ↗
#6E8AD8
matter
src ↗
#3a5cb8
matter
src ↗
#1f8a5b
brand
src ↗
rgba(31, 138, 91, 0.1)
brand
src ↗
rgba(31, 138, 91, 0.35)
brand
src ↗
#6b7280
brand
src ↗
rgba(107, 114, 128, 0.1)
brand
src ↗
rgba(107, 114, 128, 0.3)
brand
src ↗
#b0322b
brand
src ↗
rgba(176, 50, 43, 0.1)
brand
src ↗
rgba(176, 50, 43, 0.35)
brand
src ↗
No tokens match.

Done-indicator fill reads --status-green. Active-indicator inner dot reads --fg. Pending border reads --ink-18. Done-label line-through reads --ink-28.

Accessibility

  • Keyboard interactions. None — the card is non-interactive.
  • ARIA roles and properties. The <ol> carries its native list semantics. Indicators are decorative spans (aria-hidden). The visible character on done items is text, so screen readers announce it.
  • Focus order. N/A.
  • Screen-reader expectations. Reading order: title, "{completed} of {total}", then each item in order. The ordered-list semantics implicitly announce "1.", "2.", etc.
  • Reduced motion. No keyframes in steady state. If the consumer animates the completed cursor advancing, the motion contract requires the transition to collapse to opacity under reduced motion.
  • Forced colors. Done fill maps to ButtonText; active inner dot to Highlight; pending border to ButtonBorder.
  • WIG rules. <ol> over a <div> list (semantic-element rule). Color is not the sole differentiator — done items also carry line-through; active items carry the inner-dot pattern.

Do / Don't

Do — keep items short and verb-led ("Compute fee", "Generate AR", "Submit"). Each item is a step name, not a sentence.
Don't — pass items longer than ~8 entries. Beyond that, the card grows past the inline-reply budget — use a dedicated plan page.
Do — advance completed monotonically as the agent finishes each step.
Don't — re-order items between renders. The ordinal sequence is the contract.
Do — set completed: items.length when the plan finishes, then transition the card away after a beat.
Don't — leave a completed plan card sitting in the chat indefinitely. The state belongs in a Timeline once done.

Recipes

This card appears in:

Code example

import { PlanCard } from "@matter/components";

export function PlanInline({ plan }: { plan: AgentPlan }) {
  return (
    <PlanCard
      title={plan.title}
      items={plan.steps.map((s) => s.label)}
      completed={plan.steps.filter((s) => s.state === "complete").length}
    />
  );
}

Source

packages/components/src/InlineCard/PlanCard

On this page