Skip to content
goodtabs UI
BrowseBrowse components

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

PropTypeDefaultDescription
status"active" | "idle" | "paused" | "failed""active"The state. Picks the shape and the default label.
labelstringReplaces 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