Components / AI interface

ApprovalCard

Human-in-the-loop questions the agent asks before acting.

Install

npx shadcn@latest add https://irisui.dev/r/approval-card.json
import ApprovalCard from "@/components/iris/approval-card/approval-card";
Registry item approval-cardBrings in foundationRegistry JSON

Preview

ApprovalCard

A human-in-the-loop card that asks one question at a time before an agent acts, with single-choice, multi-select and free-text answers.

When to use

  • Collect the decisions an agent needs before it does something with side effects, such as which story to draft or who signs it off.
  • Walk through a short set of questions (2–5) in sequence. Single-choice answers advance on their own and multi-select answers wait for Continue.
  • Not for confirming one destructive action — use Dialog, or HoldToConfirm for an inline guard. Not for proposing a single best option with a confidence level — use RecommendationCard.

Usage

import ApprovalCard from "@/components/iris/approval-card/approval-card";

h(ApprovalCard, {
  questions: [
    {
      q: "Which story should I draft first?",
      type: "radio",
      options: ["Greek bond yields", "ECB rate signals", "Ferry strike"],
    },
    { q: "Which formats should I compose?", type: "check", options: ["Quick brief", "Full story", "Digest block"] },
  ],
  onSubmitted: (answers) => composeDrafts(answers),
});

Props

Prop Type Default Description
questions { q: string; type: "radio" | "check"; options: string[] }[] 3 newsroom questions (story, formats, sign-off) Questions shown in order. radio is single choice and advances 480ms after a pick. check is multi-select.
labels Partial<{ skip, continue, send, customPlaceholder, sentMessage }> Skip, Continue, Send, Something else…, Answers sent Copy for the footer buttons, the free-text row and the sent confirmation.
onSubmitted (answers: Record<number, number[]>) => void — Called on Send with selected option indexes keyed by question index.
onAnswerChange (questionIndex: number, answer: number[]) => void — Called whenever a selection changes.
resettable boolean true Shows "Start over" after the answers are sent.
variant string — Accepted for gallery parity. It has no effect.

Notes

  • Every question has a free-text "Something else…" row. Typing in it clears a radio selection, and Enter submits once there is an answer.
  • onSubmitted receives option indexes only. Free-text answers are kept in internal state and are not passed to the callback.
  • The dismiss (×) button collapses the card to an "Open approval" button. Skip on the last question does the same.
  • The question region is aria-live="polite". Options are buttons with aria-pressed, and inactive questions are aria-hidden and removed from the tab order.
  • The stack slides and the step counter rolls like an odometer. Under prefers-reduced-motion the slide is skipped.
  • Reads --surface, --ink, --ink-2, --ink-3, --line-strong, --hover, --green/--green-tint and the Button (ghost Skip, accent Continue/Send).