Permission Card

The permission prompt rendered in the thread: what the agent wants to do, the command it wants to run, and the three answers every agent product converged on — allow once, allow for the session, deny. Once decided, the card settles into a quiet receipt so the thread keeps an audit trail.

Run command
The agent wants to run a shell command in the worktree.
bun run test --filter billing
tsx
"use client"

import * as React from "react"

import {
  PermissionCard,
  PermissionCardCommand,
  type PermissionDecision,
} from "@/registry/seam/ui/permission-card"
import { Button } from "@/registry/seam/ui/button"

// Decide and the card settles into its receipt — the thread keeps the trail.
export default function PermissionCardDemo() {
  const [decision, setDecision] = React.useState<
    PermissionDecision | undefined
  >(undefined)

  return (
    <div className="flex flex-col items-start gap-3">
      <PermissionCard
        title="Run command"
        description="The agent wants to run a shell command in the worktree."
        decision={decision}
        onAllow={(scope) =>
          setDecision(scope === "session" ? "allowed-session" : "allowed")
        }
        onDeny={() => setDecision("denied")}
      >
        <PermissionCardCommand>
          bun run test --filter billing
        </PermissionCardCommand>
      </PermissionCard>
      {decision ? (
        <Button
          variant="ghost"
          size="sm"
          className="text-muted-foreground text-xs"
          onClick={() => setDecision(undefined)}
        >
          Reset demo
        </Button>
      ) : null}
    </div>
  )
}
bunx --bun @seamui/cli@latest add permission-card

Notes

  • Pending is a raised key demanding attention; the command sits in a debossed well (PermissionCardCommand for one-liners — compose code-block for bigger payloads); action keys come from Button. Deciding settles the card: actions are replaced by a debossed receipt.
  • Controlled and transport-agnostic: onAllow("once" | "session") / onDeny() out, decision in (allowed, allowed-session, denied, auto for auto-approved receipts).
  • The card is a labeled role="group"; the decision region is aria-live="polite", so the resolution is announced without interrupting. step renders a “2 of 3” chip that announces as “Question 2 of 3”.

Press feedback, reduced motion, and haptics follow the global policy — see Motion and Haptics.