seamui
uiagent-statussource: ok · hit
Agent Status
The canonical agent-state indicator — waiting/working/ready/done/error, as dot or chip.
npx shadcn add "https://seamui.dev/r/agent-status.json"free
Where it goes
stats
Interaction
animated-counter
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lighticonslucide-react
Registry dependencies
https://seamui.dev/r/utils.jsonhttps://seamui.dev/r/badge.json
Files1 · source cached 10/14/2026, 11:24:17 AM
registry/seam/ui/agent-status.tsx
import type * as React from "react"
import { Check, CircleAlert, Eye, X } from "lucide-react"
import { cn } from "@/lib/utils"
import { Badge } from "./badge"
// The canonical agent-state machine — the vocabulary session sidebars,
// headers, hover cards, and terminal blocks all share:
// waiting (on you) / working / ready (to review) / done / error.
type AgentState = "waiting" | "working" | "ready" | "done" | "error"
// The theme is monochrome, so state reads through shape + animation, not hue:
// waiting = filled + attention halo, working = filled + ambient pulse,
// ready = hollow, done = faint, error = the one sanctioned color.
// The working pulse is CSS animate-pulse — the Spinner precedent for ambient
// loops: opacity-only, so it reads identically under reduced motion, and it
// keeps running inside AnimatePresence parents where motion.dev repeat stalls.
const DOT: Record<AgentState, string> = {
waiting: "bg-primary ring-[3px] ring-primary/20",
working: "bg-primary animate-pulse",
ready: "border-2 border-primary bg-transparent",
done: "bg-muted-foreground/40",
error: "bg-destructive",
}
const LABELS: Record<AgentState, string> = {
waiting: "Waiting on you",
working: "Working",
ready: "Ready to review",
done: "Done",
error: "Error",
}
// Bare dot for dense rows (session lists, tab strips). With an aria-label it
// announces as a status; without one it's decoration next to visible text.
function AgentStatusDot({
status,
className,
...props
}: React.ComponentProps<"span"> & { status: AgentState }) {
const labelled = props["aria-label"] !== undefined
return (
<span
data-slot="agent-status-dot"
data-status={status}
role={labelled ? "status" : undefined}
aria-hidden={labelled ? undefined : true}
className={cn(
"inline-block size-2 shrink-0 rounded-full",
DOT[status],
className
)}
{...props}
/>
)
}
const CHIP: Record<
AgentState,
{
variant: React.ComponentProps<typeof Badge>["variant"]
icon: React.ReactNode
}
> = {
// waiting is the loudest thing in a row — an embossed primary key.
waiting: { variant: "default", icon: <CircleAlert className="size-3" /> },
working: {
variant: "secondary",
icon: <AgentStatusDot status="working" className="size-1.5" />,
},
ready: { variant: "secondary", icon: <Eye className="size-3" /> },
// done settles into the surface — the debossed muted chip.
done: { variant: "muted", icon: <Check className="size-3" /> },
error: { variant: "destructive", icon: <X className="size-3" /> },
}
// Labeled chip on Badge — the ToolStatus pattern promoted to session level.
// Announced politely so a state change is heard without interrupting.
function AgentStatus({
status,
className,
children,
...props
}: Omit<React.ComponentProps<typeof Badge>, "variant"> & {
status: AgentState
}) {
const s = CHIP[status]
return (
<Badge
data-slot="agent-status"
data-status={status}
variant={s.variant}
aria-live="polite"
className={cn("gap-1.5", className)}
{...props}
>
{s.icon}
{children ?? LABELS[status]}
</Badge>
)
}
export { AgentStatus, AgentStatusDot, type AgentState }