docs / core / stat-chip

StatChip

a mini metric in a pill: a label, a figure, an optional unit. tone colors only the value, so a row of them scans as numbers, not a rainbow.

intensity · recedesrsc · server

provenance

shaped from sisu-plus

layer

core / primitives

intensity

recedes · safe anywhere, including dense surfaces

composition

a wrapping row of metrics in a card header or dashboard rowmixed tones side by side; the neutral chip is the resting statenot a status indicator. a state without a figure is Badgenot clickable. it renders a span; a metric that navigates gets wrapped, not wired

a11y

a plain span: label, value and unit read in order · tone is never the only signal, the label names the metricjest-axe

dependencies

class-variance-authority
tones
total1,240active86done99%uptime99.9%due4failed2
sizes
credits142crcredits142cr

props

proptypedefaultnotes
valueReactNodethe figure. semibold, tone-colored.
labelReactNodeleading muted label, e.g. "credits".
unitReactNodetrailing faint unit, e.g. "cr", "%".
tone"neutral" | "accent" | "accent-alt" | "success" | "warning" | "danger""neutral"tints the border and colors the value.
size"sm" | "md""md"sm drops to a 24px pill for the densest rows.

get it

npx shadcn add https://usva.build/r/stat-chip.jsoncopy
source · components/ui/stat-chip.tsxexactly what this command copiescopy
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";

export const statChipVariants = cva(
  cn(
    "inline-flex items-center gap-1.5 rounded-full border px-2.5 font-mono tabular-nums",
    "transition-tint duration-fast ease-soft",
  ),
  {
    variants: {
      tone: {
        neutral: "border-border bg-surface-2",
        accent: "border-accent/25 bg-accent/8",
        "accent-alt": "border-accent-alt/25 bg-accent-alt/8",
        success: "border-success/25 bg-success/8",
        warning: "border-warning/25 bg-warning/8",
        danger: "border-danger/25 bg-danger/8",
      },
      size: {
        sm: "h-6 text-[0.6875rem]",
        md: "h-7 text-xs",
      },
    },
    defaultVariants: { tone: "neutral", size: "md" },
  },
);

const valueTone: Record<string, string> = {
  neutral: "text-ink",
  accent: "text-accent",
  "accent-alt": "text-accent-alt",
  success: "text-success",
  warning: "text-warning",
  danger: "text-danger",
};

export interface StatChipProps
  extends React.HTMLAttributes<HTMLSpanElement>,
    VariantProps<typeof statChipVariants> {
  /** Leading muted label, e.g. "credits". */
  label?: React.ReactNode;
  /** The figure. Rendered semibold and tabular. */
  value: React.ReactNode;
  /** Trailing faint unit, e.g. "cr", "%". */
  unit?: React.ReactNode;
}

export function StatChip({
  className,
  tone = "neutral",
  size,
  label,
  value,
  unit,
  ...props
}: StatChipProps) {
  return (
    <span
      className={cn(statChipVariants({ tone, size }), className)}
      {...props}
    >
      {label != null && (
        <span className="uppercase tracking-wide text-muted">{label}</span>
      )}
      <span className={cn("font-semibold", valueTone[tone ?? "neutral"])}>
        {value}
      </span>
      {unit != null && <span className="text-muted">{unit}</span>}
    </span>
  );
}
StatChip.displayName = "StatChip";