← Orion

registry:ui

Badge

A compact label chip for filters, statuses, and countable tags.

Rendered example

Ready

Install

orion-themedot
Open registry JSON

registry/ui/badge.tsx

/**
 * BADGE
 * A compact label chip for filters, statuses, and countable tags.
 *
 * Variants:
 *   pill  — rounded-full, for filter tabs like "All" / "To Do"
 *   status — rounded-[5px] tint, for inline status labels
 *   chip  — rounded-full with shadow-hairline, for informational chips
 *
 * Usage:
 *   <Badge>All</Badge>
 *   <Badge variant="status" tone="orange">To do</Badge>
 *   <Badge variant="chip">Draft</Badge>
 *   <Badge active={false}>To Do</Badge>
 *   <Badge count={2}>To Do</Badge>
 */

import type React from "react"

import { Dot } from "@/registry/ui/dot"

const STATUS_STYLES = {
  orange: "bg-orange-tint text-orange",
  accent: "bg-accent-tint text-accent-ink",
  green: "bg-green-tint text-green",
  red: "bg-red-tint text-red",
} as const

export type BadgeTone = keyof typeof STATUS_STYLES

export function Badge({
  children,
  variant = "pill",
  tone,
  active = true,
  count,
  dotColor,
  onClick,
  style,
  className,
}: {
  children: React.ReactNode
  variant?: "pill" | "status" | "chip"
  tone?: BadgeTone
  active?: boolean
  count?: number
  dotColor?: string
  onClick?: () => void
  style?: React.CSSProperties
  className?: string
}) {
  const shared = { className: className ?? undefined, style } as const

  if (variant === "status" && tone) {
    return (
      <span
        {...shared}
        className={`inline-flex h-5 items-center rounded-[5px] px-1.5 text-[11px] font-medium ${STATUS_STYLES[tone]} ${className ?? ""}`}
      >
        {children}
      </span>
    )
  }

  if (variant === "chip") {
    return (
      <span
        {...shared}
        className={`inline-flex h-5.5 items-center gap-1.5 rounded-full bg-inset px-2 text-[11.5px] font-medium shadow-hairline ${className ?? ""}`}
      >
        {dotColor && <Dot color={dotColor} />}
        {children}
      </span>
    )
  }

  // pill variant
  return (
    <button
      type="button"
      aria-pressed={active}
      {...shared}
      className={`flex h-6.5 shrink-0 items-center gap-1.5 rounded-full px-2.5 text-[12px] font-medium transition-[background-color,box-shadow,color] duration-200 ${
        active ? "bg-surface text-ink shadow-btn" : "text-ink-2 hover:bg-hover"
      } ${className ?? ""}`}
      onClick={onClick}
    >
      {dotColor && <Dot color={dotColor} />}
      {children}
      {count !== undefined && (
        <span className={`text-[10.5px] tabular-nums ${active ? "text-ink-2" : "text-ink-3"}`}>{count}</span>
      )}
    </button>
  )
}