Components
AgentStatus
The run’s state in one pill, announced to screen readers.
Example
Interactive, and the same on the components page. Try the keyboard below on it.
Installation
npm i signoff-ui aiimport { AgentStatus, deriveAgentState } from 'signoff-ui';
// Once per app (with Tailwind v4: @import 'signoff-ui/tailwind.css'; in your CSS)
import 'signoff-ui/styles.css';npx shadcn@latest add https://signoff-ui.vercel.app/r/agent-status.jsonimport { AgentStatus } from '@/components/signoff-ui/agent-status';
import { deriveAgentState } from '@/components/signoff-ui/lib/ai';The styles are once per app; Getting started has the Tailwind v4 and plain CSS options.
Usage
const last = messages.findLast((m) => m.role === 'assistant');
// Waiting on a person (an approval, or a client-side tool in pendingClientTools) wins over "working".
// A run that ended with text still streaming or a call unfinished, as stop() leaves it, is "stopped";
// a client-side tool the app runs itself (onToolCall), named in clientTools, reads "working" instead.
const { state, detail } = deriveAgentState({
status,
message: last,
pendingClientTools: ['review_changes'],
clientTools: ['get_location'],
});
<AgentStatus state={state} detail={detail} />How it works
Thinking, working, waiting for approval, done, stopped or error, with an elapsed timer. Changes are announced through a live region: debounced so quick flips are not read out one by one, and assertive only for approvals and errors.
deriveAgentState maps useChat's status and the latest message to a state, with human-in-the-loop waits taking precedence. A run that ended with work unfinished, which is what stop() leaves behind (text or reasoning still streaming, a tool call still preparing or running), is stopped rather than done.
Client-side tools change that. Name the ones that wait on a person, such as a review, in pendingClientTools: they read awaiting-approval. Name the ones the app runs itself (onToolCall, then addToolOutput) in clientTools: while one runs, useChat is ready, and the run reads working with the tool as its detail.
API reference
Generated from the types the package ships, so it matches the version you install.
AgentStatus
A compact, live status pill for an agent run. State changes are announced through a polite live region (assertive for errors and approval requests), debounced so rapid transitions do not flood screen readers.
Props
staterequiredAgentState'idle' | 'thinking' | 'working' | 'awaiting-approval' | 'done' | 'stopped' | 'error'labelstringOverrides the state's name (
labels.agentStatus.states).detailstringSecondary detail, e.g. the tool being run.
startedAtnumberEpoch ms when the run started. Shows a live elapsed timer while active.
elapsedMsnumberFixed elapsed time to show (e.g. the final run duration). Takes precedence over
startedAt.announcebooleanDefaulttrueAnnounce state changes to assistive tech.
size'sm' | 'md'Default'md'labelsSignoffLabelsInputWords to use instead of the English defaults: see
SignoffLabelsProvider.
Other props go to the root <div>: className (merged with tailwind-merge), id, aria-*, data-* and event handlers.
AgentState
type AgentState = 'idle' | 'thinking' | 'working' | 'awaiting-approval' | 'done' | 'stopped' | 'error';Accessibility
- Changes are announced through a live region, debounced by 350 ms so quick flips aren’t read one by one. Approvals and errors are assertive; everything else is polite.
- Render one
AgentStatusper run withannounceon; passannounce={false}to any copy, or the state is read twice. - Each state has an icon and a word. The thinking shimmer and the spinner run only without
prefers-reduced-motion.
Theming
Every color, radius and font is a CSS variable, so the overrides in Theming apply, on :root or scoped to any element. These hooks are read from the component's markup.
- Slots (data-slot)
- signoff-agent-status
- State attributes
- data-stateon
signoff-agent-status'idle' | 'thinking' | 'working' | 'awaiting-approval' | 'done' | 'stopped' | 'error' - Tokens it uses
- --signoff-accent--signoff-accent-fg--signoff-border--signoff-fg--signoff-fg-muted--signoff-fg-subtle--signoff-font-mono--signoff-font-sans--signoff-hot--signoff-hot-fg--signoff-surface
/* Only this component, and only inside .settings-panel */
.settings-panel [data-slot='signoff-agent-status'] {
--signoff-radius: 4px;
}
[data-slot='signoff-agent-status'][data-state='idle'] {
outline: 1px solid var(--signoff-accent);
}