Browse all components
Agent Status UI
Status for an AI agent working on its own — running, waiting on you, completed or failed, with what it did last.
Preview
Searching 12 sources for recent AI model releases…
Presets
Props
Also takes onStop, onRetry.
Agent promptClaude Code · Cursor · any agent
Add the Agent Status UI component from the Scrim UI registry to this project and use it in the configuration described below.
Agent Status UI — Status for an AI agent working on its own — running, waiting on you, completed or failed, with what it did last.
## 1. Install
```bash
npx shadcn@latest add @scrimui/agent-status
```
This writes a single file to `components/ui/agent-status.tsx`. It is plain React + Tailwind with no runtime dependencies — no Radix, no CVA, nothing to add to package.json. If this project does not use the shadcn CLI, copy the source from https://scrimui.dev/components/agent-status to the same path by hand; nothing in the file depends on shadcn.
## 2. Use it
```tsx
import { AgentStatus } from "@/components/ui/agent-status";
<AgentStatus
name="Research Agent"
status="running"
action="Searching 12 sources for recent AI model releases…"
progress={42}
elapsed="8s"
onStop={() => {}}
onRetry={() => {}}
/>
```
## 3. The configuration that matters
This is the component's "Running" state — Progress bar, elapsed time and a stop control; the action line names the current step.
Every prop is at its default value. Keep the call site minimal — do not write out default values.
## 4. Wire the handlers
`onStop`, `onRetry` are stubs in the snippet above (`() => {}`). Connect them to this project's own state and data layer instead of shipping the empty functions.
Reference: https://scrimui.dev/components/agent-statusFollows the props below — change a control and the prompt changes with it, so an agent reproduces that configuration instead of the defaults.
Install
npx shadcn@latest add @scrimui/agent-statusComponent source
React + Tailwind component using shadcn/ui (badge, button, card). The install command adds these dependencies automatically.
When copying source manually, install the required primitives first:
npx shadcn@latest add badge button card"use client";
import { Card } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import * as React from "react";
/* ------------------------------------------------------------------ */
/* Types */
/* ------------------------------------------------------------------ */
export type AgentState = "running" | "waiting" | "completed" | "failed";
export type AgentStatusProps = {
name: string;
status: AgentState;
action?: string;
progress?: number;
elapsed?: string;
icon?: React.ReactNode;
onStop?: () => void;
onRetry?: () => void;
className?: string;
};
/* ------------------------------------------------------------------ */
/* Icons */
/* ------------------------------------------------------------------ */
function BotIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="16" height="16" {...props}>
<rect width="18" height="10" x="3" y="8" rx="2" />
<path d="M12 8V4a2 2 0 0 1 2-2h2" />
<circle cx="9" cy="13" r="0.5" fill="currentColor" />
<circle cx="15" cy="13" r="0.5" fill="currentColor" />
<path d="M5 11v6M19 11v6M9 17h6" />
</svg>
);
}
function CheckIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="12" height="12" {...props}>
<path d="M20 6 9 17l-5-5" />
</svg>
);
}
function XIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="12" height="12" {...props}>
<path d="M18 6 6 18M6 6l12 12" />
</svg>
);
}
function PauseIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="currentColor" width="11" height="11" {...props}>
<rect x="6" y="5" width="4" height="14" rx="1" />
<rect x="14" y="5" width="4" height="14" rx="1" />
</svg>
);
}
function StopIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" fill="currentColor" width="10" height="10" {...props}>
<rect x="6" y="6" width="12" height="12" rx="2" />
</svg>
);
}
/* ------------------------------------------------------------------ */
/* Status pill + progress */
/* ------------------------------------------------------------------ */
const STATUS_STYLES: Record<AgentState, { pill: string; icon: React.ReactNode; label: string }> = {
running: {
pill: "bg-muted text-foreground",
icon: (
<span className="h-2.5 w-2.5 animate-spin rounded-full border-[1.5px] border-current border-t-transparent" />
),
label: "Running",
},
waiting: {
pill: "bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-400",
icon: <PauseIcon />,
label: "Waiting",
},
completed: {
pill: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-400",
icon: <CheckIcon />,
label: "Completed",
},
failed: {
pill: "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-400",
icon: <XIcon />,
label: "Failed",
},
};
/* ------------------------------------------------------------------ */
/* AgentStatus */
/* ------------------------------------------------------------------ */
export function AgentStatus({
name,
status,
action,
progress,
elapsed,
icon,
onStop,
onRetry,
className = "",
}: AgentStatusProps) {
const s = STATUS_STYLES[status];
return (
<Card
className={`gap-0 py-0 rounded-xl border border-border bg-card p-3.5 transition-colors ${
status === "failed"
? "border-red-200 dark:border-red-900/60"
: status === "running"
? "border-border"
: ""
} ${className}`}
>
<div className="flex items-center gap-3">
<span
className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-lg ${
status === "failed"
? "bg-red-100 text-red-600 dark:bg-red-900/40 dark:text-red-400"
: status === "completed"
? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400"
: "bg-muted text-muted-foreground"
}`}
>
{icon ?? <BotIcon />}
</span>
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
<span className="truncate text-sm font-medium text-foreground">
{name}
</span>
<Badge variant="secondary" className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs ${s.pill}`}>
{s.icon}
{s.label}
</Badge>
{elapsed && <span className="text-xs tabular-nums text-muted-foreground">{elapsed}</span>}
</div>
{action && (
<p className="mt-0.5 truncate text-sm text-muted-foreground">{action}</p>
)}
</div>
{status === "running" && onStop && (
<Button variant="outline" size="icon-sm"
type="button"
onClick={onStop}
aria-label={`Stop ${name}`}
className="min-h-6 min-w-6 flex h-8 w-7 shrink-0 items-center justify-center rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
>
<StopIcon />
</Button>
)}
{status === "failed" && onRetry && (
<Button variant="outline" size="sm"
type="button"
onClick={onRetry}
className="inline-flex h-8 shrink-0 items-center gap-1.5 rounded-md border border-border px-2.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="12" height="12">
<path d="M21 12a9 9 0 1 1-2.64-6.36L21 8" />
<path d="M21 3v5h-5" />
</svg>
Retry
</Button>
)}
</div>
{/* Progress bar */}
{status === "running" && progress !== undefined && (
<div className="mt-3 h-1 overflow-hidden rounded-full bg-muted">
<div
className="h-full rounded-full bg-primary transition-[color,background-color,border-color,box-shadow] duration-300"
style={{ width: `${Math.min(100, Math.max(0, progress))}%` }}
/>
</div>
)}
</Card>
);
}
When to use it
- Stack one card per agent when several run at once — each keeps its own lifecycle, and the panel becomes a fleet view rather than a single status line.
- Name the current action in plain language — users trust agents that narrate what they're doing.
- Show a real progress bar only when progress is measurable; otherwise use elapsed time, not a fake bar.
- Reserve waiting (amber) for true human or external dependencies, not for loading.
- On failure, say which step failed and offer retry — never just 'something went wrong'.
- Color the status pill, keep the row itself calm — a wall of colored cards is noise.
What breaks in production
- Animating a progress bar that isn't backed by real progress — users learn to distrust it.
- Hiding agent activity entirely until completion — an agent that runs silently feels like a hang.
- Using 'waiting' as a synonym for 'loading' — it misleads about who's holding things up.
- No retry affordance on failure, forcing the user to restart the whole run.
Guides
The moment a tool call becomes consequential, an agent needs a human gate. Here is when to pause, what to show in the approval prompt, and how to keep approval from turning into a rubber stamp.
A task that runs for minutes needs more than a spinner: live progress, visible steps, and an escape hatch. Here is how the products that ship long-running agents keep users oriented — and in control.
Related Components
The human-in-the-loop confirmation — what the agent wants to do, the exact details, and allow or deny before it acts.
The approval card as a lifecycle: a decision that survives a closed tab, is idempotent across two open ones, and says what happened when it lands after the request expired.
The checklist an agent writes for itself and then edits mid-run — steps added, skipped and re-ordered, without the list jumping under the reader.
One agent passing work to another — who asked, what was carried across, and what the receiving agent was not told.