Base
Badge
Badge renders a compact inline label for statuses, counts, or tags.
Installation
npx shadcn add https://rck.vibeboxph.com//r/badge.jsonLive preview & controls
Badge
Controls
<Badge>Badge</Badge>Anatomy
BadgeThe root <span> (or Slot target when asChild).
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "solid" | "soft" | "outline" | "neutral" | "success" | "warning" | "destructive" | soft | Visual style, including semantic status tones. |
| size | "xs" | "sm" | "md" | "lg" | md | Padding and font size. |
| dot | boolean | — | Shows a status dot before the label for live indicators. |
| asChild | boolean | false | Render the child element instead of a <span>. |
| className | string | — | Merged 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
VariantsSolid, soft, outline, neutral and semantic tones.
SizesExtra small through large at a consistent variant.
Status dotLive indicators with the `dot` prop.
Layout useBadges paired with labels at different sizes.
As linkStyled anchors via `badgeVariants` for tag navigation.