Status dot
A state mark with its word beside it, so colour never carries the state.
npx shadcn@latest add https://ui.goodtabs.co/r/status-dot.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/status-dot
Deployed
A mark and a label.
All four states
ActiveIdlePausedFailed
Each state has its own shape: dot, ring, half ring, diamond.
In a list
- Registry buildPassing
- Screenshot workerWaiting for a job
- Search indexPaused for reindex
- Newsletter syncFailed 2 hours ago
Right aligned in a row of services.
Usage
import { StatusDot } from "@/components/ui/status-dot"
<StatusDot status="active" label="Deployed" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| status | "active" | "idle" | "paused" | "failed" | "active" | The state. Picks the shape and the default label. |
| label | string | Replaces the default label. |
Source
status-dot/status-dot.tsx
import * as React from "react";
import { cn } from "@/lib/utils";
type Status = "active" | "idle" | "paused" | "failed";
type StatusDotProps = Omit<React.ComponentProps<"span">, "children"> & {
/** The state. Each one has its own shape and its own default label. */
status?: Status;
/** Replaces the default label, for example "Deployed". */
label?: string;
};
const LABEL = { active: "Active", idle: "Idle", paused: "Paused", failed: "Failed" } as const;
function Mark({ status }: { status: Status }) {
return (
<svg viewBox="0 0 8 8" className="size-2 shrink-0" aria-hidden="true">
{status === "active" && <circle cx="4" cy="4" r="4" className="fill-brand" />}
{status === "idle" && (
<circle cx="4" cy="4" r="3.25" className="fill-none stroke-muted-foreground" strokeWidth="1.5" />
)}
{status === "paused" && (
<>
<circle cx="4" cy="4" r="3.25" className="fill-none stroke-foreground" strokeWidth="1.5" />
<path d="M4 .75a3.25 3.25 0 0 1 0 6.5z" className="fill-foreground" />
</>
)}
{status === "failed" && <path d="M4 0 8 4 4 8 0 4z" className="fill-foreground" />}
</svg>
);
}
/**
* A state mark with its word beside it. The shape differs per state and the
* label is always rendered, so colour never carries the meaning on its own.
*/
export function StatusDot({ status = "active", label, className, ...props }: StatusDotProps) {
return (
<span className={cn("inline-flex items-center gap-1.5 text-sm text-foreground", className)} {...props}>
<Mark status={status} />
{label ?? LABEL[status]}
</span>
);
}
For AI agents
Hand your agent this page, the markdown at /components/status-dot/prompt.md, or the Copy AI prompt button above.
When to use
- Rows of services, builds, members or domains where a state matters
- Anywhere a coloured dot would be the only signal
When not to use
- A count or a category: use a badge
- Long state descriptions: put them in the row text
Agent notes
- The label is always rendered; never hide it
- Only the active mark uses --brand; the rest are foreground tones, told apart by shape
- Extends a span, so className passes through