Base

Badge

Badge renders a compact inline label for statuses, counts, or tags.

Installation

npx shadcn add https://rck.vibeboxph.com//r/badge.json

Live preview & controls

Badge
Controls
<Badge>Badge</Badge>

Anatomy

  • BadgeThe root <span> (or Slot target when asChild).

Props

PropTypeDefaultDescription
variant"solid" | "soft" | "outline" | "neutral" | "success" | "warning" | "destructive"softVisual style, including semantic status tones.
size"xs" | "sm" | "md" | "lg"mdPadding and font size.
dotbooleanShows a status dot before the label for live indicators.
asChildbooleanfalseRender the child element instead of a <span>.
classNamestringMerged with the generated variant classes.

Accessibility

  • Purely presentational by default (no implicit role); add `role="status"` yourself if a badge communicates a live update.
  • When using `dot` for status, the dot is `aria-hidden` — the text label must still convey the status in words.
  • For filter/tag navigation, use `badgeVariants` on a plain `<a>` or `asChild` so the element keeps link semantics.
  • Color is never the only signal — pair semantic variants with descriptive text, as shown in every example.

Source

registry/rck/ui/badge.tsx
import * as React from "react";
import { cva, type VariantProps } from "@/lib/cva";
import { Slot } from "@/registry/rck/primitives/slot";
import { cn } from "@/lib/utils";

export const badgeVariants = cva(
  "inline-flex w-fit items-center gap-1 rounded-full border font-medium " +
    "whitespace-nowrap [&_svg]:size-3 [&_svg]:pointer-events-none [&_svg]:shrink-0",
  {
    variants: {
      variant: {
        solid: "border-transparent bg-primary text-primary-foreground",
        soft: "border-transparent bg-primary-soft text-primary-soft-foreground",
        outline: "border-border-strong bg-transparent text-foreground",
        neutral: "border-transparent bg-surface-2 text-foreground",
        success: "border-transparent bg-success/15 text-success",
        warning: "border-transparent bg-warning/15 text-warning",
        destructive: "border-transparent bg-destructive/15 text-destructive",
      },
      size: {
        xs: "px-1.5 py-px text-[10px] [&_svg]:size-2.5",
        sm: "px-2 py-0.5 text-[11px]",
        md: "px-2.5 py-0.5 text-xs",
        lg: "px-3 py-1 text-sm",
      },
    },
    defaultVariants: {
      variant: "soft",
      size: "md",
    },
  },
);

const dotToneClasses = {
  solid: "bg-primary-foreground",
  soft: "bg-primary-soft-foreground",
  outline: "bg-foreground",
  neutral: "bg-foreground",
  success: "bg-success",
  warning: "bg-warning",
  destructive: "bg-destructive",
} as const;

const dotSizeClasses = {
  xs: "size-1",
  sm: "size-1.5",
  md: "size-1.5",
  lg: "size-2",
} as const;

export interface BadgeProps
  extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof badgeVariants> {
  asChild?: boolean;
  /** Renders a small status dot before the label — useful for live indicators. */
  dot?: boolean;
}

export const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
  (
    { className, variant = "soft", size, asChild = false, dot, children, ...props },
    ref,
  ) => {
    const Comp = asChild ? Slot : "span";
    const resolvedVariant = variant ?? "soft";
    const resolvedSize = size ?? "md";

    return (
      <Comp
        ref={ref}
        data-slot="badge"
        className={cn(badgeVariants({ variant, size }), className)}
        {...props}
      >
        {asChild ? (
          children
        ) : (
          <>
            {dot && (
              <span
                aria-hidden="true"
                className={cn(
                  "shrink-0 rounded-full",
                  dotSizeClasses[resolvedSize],
                  dotToneClasses[resolvedVariant],
                )}
              />
            )}
            {children}
          </>
        )}
      </Comp>
    );
  },
);
Badge.displayName = "Badge";

Related examples

Browse the full examples gallery