Skip to content
ssntl/ui

Tag

A status pill where the state is always a word; the dot only supports it.

LiveNewPayment failed2026

Installation

$ pnpm dlx shadcn@latest add @ssntl/tag

Usage

tsx
import { Tag } from "@/components/ui/tag"

<Tag tone="live">Live</Tag>
<Tag tone="danger">Payment failed</Tag>
  • State always carries a word: the dot only supports it, so it reads without color.
  • live for available or done, accent for New, danger for errors; plain for years and counts.
  • Don't use tags for ordinary metadata that reads fine as text.

Code

components/ui/tag.tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

// Essential Design `es-tag`: a 28px pill on `secondary`. State always carries a
// word, never color alone: the dot is decorative, the text is the signal.
const tagVariants = cva(
  "inline-flex h-7 shrink-0 items-center gap-2 rounded-full bg-secondary px-3 text-caption font-medium whitespace-nowrap",
  {
    variants: {
      tone: {
        neutral: "text-muted-foreground",
        live: "text-green-600 dark:text-green-400 [&_[data-dot]]:bg-green-400",
        accent: "text-foreground [&_[data-dot]]:bg-blue-600",
        danger: "text-destructive [&_[data-dot]]:bg-destructive",
      },
    },
    defaultVariants: { tone: "neutral" },
  }
)

function Tag({
  tone,
  dot = tone !== "neutral" && tone !== undefined,
  className,
  children,
  ...props
}: React.ComponentProps<"span"> &
  VariantProps<typeof tagVariants> & {
    /** A 7px dot before the word. On by default for live, accent and danger. */
    dot?: boolean
  }) {
  return (
    <span
      data-slot="tag"
      className={cn(tagVariants({ tone, className }))}
      {...props}
    >
      {dot && (
        <span
          aria-hidden
          data-dot
          className="size-1.5 shrink-0 rounded-full bg-current"
        />
      )}
      {children}
    </span>
  )
}

export { Tag, tagVariants }