Use Badge for a short status or category, not a primary action. This implementation supports two variants. For a destination, the render element must contain the visible content.
import { Badge } from "@/components/ui/badge";
export default function BadgeDemo() {
return (
<div className="flex flex-wrap items-center gap-2">
<Badge>Published</Badge>
<Badge variant="neutral">Documentation</Badge>
<Badge variant="neutral">v0.1.0</Badge>
</div>
);
}
#Installation
Complete the base setup, then use the CLI or copy the source:
First time? Install the shared base once.
npx shadcn@latest add https://neobrutal-ui.andongmin.com/r/badge.jsonnpm install @base-ui/react@^1.8.0Create components/ui/badge.tsx:
"use client";
import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-base border-2 border-border px-2.5 py-0.5 text-xs font-base w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] overflow-hidden",
{
variants: {
variant: {
default: "bg-main text-main-foreground",
neutral: "bg-secondary-background text-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
);
function Badge({
className,
variant,
render,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & {
render?: React.ReactElement;
}) {
return useRender({
defaultTagName: "span",
render,
props: {
"data-slot": "badge",
...props,
className: cn(badgeVariants({ variant }), className),
},
});
}
export { Badge, badgeVariants };
#Usage
import { Badge } from "@/components/ui/badge";
export default function ProjectBadges() {
return (
<div className="flex flex-wrap gap-2">
<Badge>Published</Badge>
<Badge variant="neutral">Documentation</Badge>
<Badge render={<a href="/docs">Read the guide</a>} />
</div>
);
}#API reference
| Prop / part | Purpose | Default |
|---|---|---|
variant | default or neutral | default |
children | Short visible label | — |
render | Element containing the badge content | span |
className | Sizing and layout | — |
The render element keeps its content and composes its event handlers, styles, and ref with the badge props using Base UI.
#Accessibility
Use readable status text rather than color alone. A decorative badge need not be focusable. An interactive badge must use a real link or button and retain a meaningful accessible name. For dynamically changing status, announce the relevant update in a suitable live region rather than marking every static badge as an alert.
#Examples
#Neutral
import { Badge } from "@/components/ui/badge";
export default function BadgeNeutralDemo() {
return <Badge variant="neutral">Neutral</Badge>;
}
#Additional examples
#With icon
import { CheckIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
export default function BadgeWithIconDemo() {
return (
<Badge>
<CheckIcon />
Badge
</Badge>
);
}