A small pill label in four variants with an optional status dot and success, warning and error tones. Style links with badgeVariants().
import { ArrowUpRight } from "lucide-react"
import { Badge, badgeVariants } from "@/components/ballmac/badge"
export default function BadgeDemo() {
return (
<div className="flex flex-wrap items-center justify-center gap-2">
<Badge>New</Badge>
<Badge variant="secondary">v2.4.0</Badge>
<Badge variant="outline">Pro plan</Badge>
<Badge variant="destructive">Overdue</Badge>
<Badge variant="outline" dot>
Draft
</Badge>
<a href="#changelog" className={badgeVariants({ variant: "outline" })}>
Changelog <ArrowUpRight aria-hidden="true" className="rtl:-scale-x-100"/>
</a>
</div>
)
}Installation
$ pnpm dlx shadcn@latest add @ballmac/badgeInstall the dependencies.
$ pnpm add radix-ui class-variance-authorityCopy the source into your project.
components/ballmac/badge.tsx// Ballmac UI: Badge. https://ui.ballmac.com/components/badge // Based on shadcn/ui's Badge (MIT, Copyright (c) 2023 shadcn), restyled with a status dot and status tones. import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { Slot } from "radix-ui" import { cn } from "@/lib/utils" const badgeVariants = cva( "inline-flex w-fit shrink-0 items-center justify-center gap-1.5 overflow-hidden whitespace-nowrap rounded-full border px-2 py-0.5 text-xs font-medium outline-none transition-[color,background-color,border-color,box-shadow] duration-150 focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 [&>svg]:pointer-events-none [&>svg]:size-3", { variants: { variant: { default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/80", outline: "border-border bg-background text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", destructive: "border-transparent bg-destructive text-white dark:bg-destructive/60 focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90", }, // A soft tint that overrides the variant's colors. The label text stays foreground for contrast. status: { neutral: "border-border bg-muted text-foreground", success: "border-chart-2/30 bg-chart-2/10 text-foreground", warning: "border-chart-3/30 bg-chart-3/10 text-foreground", error: "border-destructive/30 bg-destructive/10 text-foreground", }, }, defaultVariants: { variant: "default" }, } ) const dotColor = { neutral: "bg-muted-foreground", success: "bg-chart-2", warning: "bg-chart-3", error: "bg-destructive", } as const type BadgeStatus = keyof typeof dotColor type BadgeProps = React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { /** Render the child element (for example a link) with badge styles. */ asChild?: boolean /** Show a small leading dot. On by default when `status` is set. */ dot?: boolean } function Badge({ className, variant = "default", status, dot, asChild = false, children, ...props }: BadgeProps) { const Comp = asChild ? Slot.Root : "span" const showDot = dot ?? !!status const dotEl = showDot ? ( <span data-slot="badge-dot" aria-hidden="true" className={cn("size-1.5 shrink-0 rounded-full", status ? dotColor[status as BadgeStatus] : "bg-current")} /> ) : null return ( <Comp data-slot="badge" data-status={status ?? undefined} className={cn(badgeVariants({ variant, status }), className)} {...props} > {dotEl} <Slot.Slottable>{children}</Slot.Slottable> </Comp> ) } export { Badge, badgeVariants, type BadgeProps, type BadgeStatus }Update the import paths to match your project setup.
Usage
import { Badge } from "@/components/ballmac/badge"The full example is in the Code tab above.
Examples
Status
- api-gatewayDeployed
- web-dashboardDegraded
- billing-workerFailed
- search-indexerQueued
import { Badge } from "@/components/ballmac/badge"
const deployments = [
{ name: "api-gateway", status: "success", label: "Deployed" },
{ name: "web-dashboard", status: "warning", label: "Degraded" },
{ name: "billing-worker", status: "error", label: "Failed" },
{ name: "search-indexer", status: "neutral", label: "Queued" },
] as const
export default function BadgeStatus() {
return (
<ul className="w-full max-w-sm divide-y rounded-xl border bg-card">
{deployments.map((d) => (
<li key={d.name} className="flex items-center justify-between gap-3 px-4 py-3">
<span className="truncate font-mono text-xs">{d.name}</span>
<Badge status={d.status}>{d.label}</Badge>
</li>
))}
</ul>
)
}API reference
| Prop | Type | Default |
|---|---|---|
asChildRender the child element (for example a link) with badge styles. | boolean | false |
dotShow a small leading dot. On by default when `status` is set. | boolean | — |
variant | "default" | "secondary" | "outline" | "destructive" | "default" |
status | "neutral" | "success" | "warning" | "error" | — |
Also accepts the standard attributes of its root element.
Use with AI
Short metadata or status label. Use variant for emphasis, status (neutral | success | warning | error) for state with a coloured dot, and badgeVariants() on an <a> for a link. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Badge (@ballmac/badge) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Status of a deployment, invoice or job
- Counts, plan names and version tags
- Small labels on cards and table rows
Not for
- Clickable filters that toggle (use a toggle or button)
- Long text (it doesn't wrap)
- Notifications that need attention (use an alert or toast)
Registry JSON: https://ui.ballmac.com/r/badge.json
Credits
Based on shadcn/ui Badge, adapted by Ballmac. Free to use in personal and commercial projects.
- Registry
- shadcn/utils
Pairs well with
Avatar
A Radix avatar with image and initials fallback, three sizes, an optional presence dot, and AvatarGroup for overlapping stacks with a +N overflow counter.
Button
A button with six variants, three sizes, a pill shape and a built-in loading state. buttonVariants() styles links the same way.
Switch
A Radix toggle switch in two sizes with a CSS-animated thumb, for settings that take effect immediately. Stops animating under reduced motion.
Accordion
Vertically stacked disclosure sections on Radix Accordion, single or multiple open, with hairline dividers, a rotating plus and height animation.