Agent Status UI
Status UI for autonomous AI agents — running, waiting, completed and failed.
agentstatusprogress
Research AgentRunning8s
Searching 12 sources for recent AI model releases…
Props
42
Also takes onStop, onRetry.
Component source
Single-file React + Tailwind component. No dependencies — drop it into any project with Tailwind configured.
agent-status.tsx
"use client";
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="2" 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="2.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="2.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-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-400",
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 (
<div
className={`rounded-xl border border-zinc-200 bg-white p-3.5 transition-colors dark:border-zinc-800 dark:bg-zinc-900 ${
status === "failed"
? "border-red-200 dark:border-red-900/60"
: status === "running"
? "border-zinc-300 dark:border-zinc-700"
: ""
} ${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-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300"
}`}
>
{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-zinc-900 dark:text-zinc-100">
{name}
</span>
<span className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] ${s.pill}`}>
{s.icon}
{s.label}
</span>
{elapsed && <span className="text-xs tabular-nums text-zinc-500 dark:text-zinc-400">{elapsed}</span>}
</div>
{action && (
<p className="mt-0.5 truncate text-[13px] text-zinc-500 dark:text-zinc-400">{action}</p>
)}
</div>
{status === "running" && onStop && (
<button
type="button"
onClick={onStop}
aria-label={`Stop ${name}`}
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg border border-zinc-200 text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-900 dark:border-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100"
>
<StopIcon />
</button>
)}
{status === "failed" && onRetry && (
<button
type="button"
onClick={onRetry}
className="inline-flex h-7 shrink-0 items-center gap-1.5 rounded-lg border border-zinc-200 px-2.5 text-xs font-medium text-zinc-600 transition-colors hover:bg-zinc-100 hover:text-zinc-900 dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-800 dark:hover:text-zinc-100"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" 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-zinc-100 dark:bg-zinc-800">
<div
className="h-full rounded-full bg-blue-500 transition-all duration-300"
style={{ width: `${Math.min(100, Math.max(0, progress))}%` }}
/>
</div>
)}
</div>
);
}
Usage Guidelines
- 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.
Common UX Mistakes
- 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.
Related Components
Approval Request
Human-in-the-loop approval card with action details and allow/deny controls.
Prompt Input
The core input for AI chat products — with attachments, model selector, tools, voice and loading states.
Prompt Input + Attachments
Prompt input with file attachment chips, upload progress and removal.
Prompt Input + Model Selector
Prompt input with an inline model picker and capability badges.