Badge
A small status indicator with multiple semantic variants.
Installation
$
Usage
import { Badge } from "@/components/ui/badge"Examples
Default
Default
Variants
DefaultSuccessWarningDestructiveMutedOutline
Sizes
SmallDefaultLarge
With Icon
FeaturedActivePendingFailedDraftLabel
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "default" | "success" | "warning" | "destructive" | "muted" | "outline" | "default" | Semantic variants tint the background and text; outline renders bordered and transparent. |
| size | "sm" | "default" | "lg" | "default" | Scales padding, text, and icon size together: sm is 11px text, default 12px, lg 13px. Every size sits a step below the control type around it, so a badge reads as a label rather than a button. |
| icon | React.ReactNode | - | Optional icon element rendered before the label, e.g. <Star />. Scales with the badge size. |
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/registry/lib/utils"
const badgeVariants = cva(
// font-medium, not semibold: 600 is Button's weight, and a badge that matches
// a control's type treatment reads as a control. Radix Themes and shadcn both
// set badges at 500.
"inline-flex items-center rounded-full border border-transparent font-medium transition-colors [&_svg]:shrink-0",
{
variants: {
variant: {
// Tinted variants read as LABELS, not controls: the text sits well past
// the tint (light -700 on -100, dark -300 on -900, both ~7:1) rather
// than at the -500 a `soft` Button uses. Darker-in-light and
// lighter-in-dark is what keeps a chip from looking clickable.
default: "bg-accent-100 dark:bg-accent-900 text-accent-700 dark:text-accent-300",
success: "bg-success-100 dark:bg-success-900 text-success-700 dark:text-success-300",
// Warning takes -800 in light, not -700: the hue is so light that
// warning-700 on warning-100 is only 3.64:1. -800 lands at 6.87:1,
// in the same band as the other tones' -700.
warning: "bg-warning-100 dark:bg-warning-900 text-warning-800 dark:text-warning-300",
destructive: "bg-destructive-100 dark:bg-destructive-900 text-destructive-700 dark:text-destructive-300",
muted: "bg-muted text-muted-foreground border-border",
outline: "border-border bg-transparent",
},
// Every size sits a step BELOW the control type it annotates: `default` is
// 12px where Button is 13px, and `lg` tops out at 13px so a badge never
// matches the body copy around it.
size: {
sm: "gap-0.5 px-1.5 py-0.5 text-2xs [&_svg]:size-2.5",
default: "gap-1 px-2 py-0.5 text-1xs [&_svg]:size-3",
lg: "gap-1.5 px-2.5 py-1 text-xs [&_svg]:size-3.5",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLSpanElement>,
VariantProps<typeof badgeVariants> {
icon?: React.ReactNode
}
function Badge({ className, variant, size, icon, children, ...props }: BadgeProps) {
return (
<span data-slot="badge" className={cn(badgeVariants({ variant, size }), className)} {...props}>
{icon}
{children}
</span>
)
}
export { Badge, badgeVariants }