Badge
A versatile badge component for displaying labels, status indicators, counts, or metadata. Built with Tailwind CSS and styled using Ocean UI design tokens for consistent theming.
Example
import { BadgeCheckIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
export default function BadgeDemo() {
return (
<div className="flex flex-col items-center gap-2">
<div className="flex w-full flex-wrap gap-2">
<Badge>Badge</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
</div>
<div className="flex w-full flex-wrap gap-2">
<Badge variant="secondary">
<BadgeCheckIcon />
Verified
</Badge>
<Badge className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums">
8
</Badge>
<Badge
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="destructive"
>
99
</Badge>
<Badge
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="outline"
>
20+
</Badge>
</div>
</div>
);
}
import { BadgeCheckIcon } from "lucide-solid";
import { Badge } from "@/components/ui/badge";
export default function BadgeDemo() {
return (
<div class="flex flex-col items-center gap-2">
<div class="flex w-full flex-wrap gap-2">
<Badge>Badge</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
</div>
<div class="flex w-full flex-wrap gap-2">
<Badge variant="secondary">
<BadgeCheckIcon />
Verified
</Badge>
<Badge class="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums">
8
</Badge>
<Badge
class="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="destructive"
>
99
</Badge>
<Badge
class="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="outline"
>
20+
</Badge>
</div>
</div>
);
}
How to install and use
Complete the manual installation setup
If you haven't already completed the first 4 steps of the manual installation guide, please do so before continuing to install these components.
Create a badge.tsx file and paste the following code into it.
import { cva, type VariantProps } from "class-variance-authority";
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 [&>svg]:pointer-events-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 transition-[color,box-shadow] overflow-hidden",
{
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/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
outline:
"border-border bg-transparent text-foreground [a&]:hover:bg-muted [a&]:hover:text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
);
export interface BadgeProps
extends ComponentProps<"span">,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<span
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
);
}
export { Badge, badgeVariants };
import { cva, type VariantProps } from "class-variance-authority";
import type { JSX, ParentComponent } from "solid-js";
import { splitProps } from "solid-js";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 [&>svg]:pointer-events-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 transition-[color,box-shadow] overflow-hidden",
{
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/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
outline:
"border-border bg-transparent text-foreground [a&]:hover:bg-muted [a&]:hover:text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
);
export interface BadgeProps
extends JSX.HTMLAttributes<HTMLSpanElement>,
VariantProps<typeof badgeVariants> {}
export const Badge: ParentComponent<BadgeProps> = (props) => {
const [local, rest] = splitProps(props, ["variant", "class"]);
return (
<span
data-slot="badge"
class={cn(badgeVariants({ variant: local.variant }), local.class)}
{...rest}
/>
);
};
export { badgeVariants };
import { cva, type VariantProps } from "class-variance-authority";
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 [&>svg]:pointer-events-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 transition-[color,box-shadow] overflow-hidden",
{
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/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
outline:
"border-border bg-transparent text-foreground [a&]:hover:bg-muted [a&]:hover:text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
);
export interface BadgeProps
extends ComponentProps<"span">,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<span
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
);
}
export { Badge, badgeVariants };
import { cva, type VariantProps } from "class-variance-authority";
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 [&>svg]:pointer-events-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 transition-[color,box-shadow] overflow-hidden",
{
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/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
outline:
"border-border bg-transparent text-foreground [a&]:hover:bg-muted [a&]:hover:text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
);
export interface BadgeProps
extends ComponentProps<"span">,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<span
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
);
}
export { Badge, badgeVariants };
Finally, Choose any example you like and add it to your project.
For instance, create a new file at components/shared/{example-component-name}.tsx, paste the example code into that file, and then import and use the component wherever you need it in your application.
The Badge component is a simple, semantic component built with Tailwind CSS using Ocean UI design tokens. It uses a pill-shaped design by default and supports multiple visual variants.
Overview
The Badge component is a small UI element used to display labels, status indicators, counts, or metadata. It features a pill-shaped design with consistent padding and typography, making it ideal for tags, status badges, notification counts, and more.
Variants
The Badge component supports four visual variants:
- default: Primary badge with brand color background
- secondary: Secondary badge with subtle background
- outline: Outlined badge with transparent background and border
- destructive: Badge for destructive or error states
Features
- Pill Shape: Rounded-full design by default for a modern look
- Icons: Support for leading and trailing icons via children
- Flexible Content: Can display text, numbers, icons, or images
- Accessibility: Semantic HTML with proper focus states
- Customizable: All styles can be overridden via
classNameprop - Design Tokens: Uses Ocean UI design tokens for consistent theming
Select Examples
Basic Badge
The basic badge demo shows all available variants along with examples of badges with icons and numeric counts.
import { BadgeCheckIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
export default function BadgeDemo() {
return (
<div className="flex flex-col items-center gap-2">
<div className="flex w-full flex-wrap gap-2">
<Badge>Badge</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
</div>
<div className="flex w-full flex-wrap gap-2">
<Badge variant="secondary">
<BadgeCheckIcon />
Verified
</Badge>
<Badge className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums">
8
</Badge>
<Badge
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="destructive"
>
99
</Badge>
<Badge
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="outline"
>
20+
</Badge>
</div>
</div>
);
}
import { BadgeCheckIcon } from "lucide-solid";
import { Badge } from "@/components/ui/badge";
export default function BadgeDemo() {
return (
<div class="flex flex-col items-center gap-2">
<div class="flex w-full flex-wrap gap-2">
<Badge>Badge</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
</div>
<div class="flex w-full flex-wrap gap-2">
<Badge variant="secondary">
<BadgeCheckIcon />
Verified
</Badge>
<Badge class="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums">
8
</Badge>
<Badge
class="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="destructive"
>
99
</Badge>
<Badge
class="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
variant="outline"
>
20+
</Badge>
</div>
</div>
);
}
Variants
All four badge variants: default, secondary, destructive, and outline. Each variant uses Ocean UI design tokens for consistent theming.
import { Badge } from "@/components/ui/badge";
export default function BadgeVariants() {
return (
<div className="flex w-full justify-center flex-wrap gap-2">
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
</div>
);
}
import { Badge } from "@/components/ui/badge";
export default function BadgeVariants() {
return (
<div class="flex w-full justify-center flex-wrap gap-2">
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
</div>
);
}
Status
Status badges with icons to indicate different states like Todo, In Progress, Done, and Cancelled. Uses design tokens for colors instead of hardcoded values.
import { Badge } from "@/components/ui/badge";
import { Check, CircleDashed, CircleDotDashed, X } from "lucide-react";
export default function BadgeStatus() {
return (
<div className="flex w-full justify-center flex-wrap gap-2">
<Badge variant="secondary" className="gap-2">
<CircleDashed /> Todo
</Badge>
<Badge className="bg-muted text-muted-foreground gap-2">
<CircleDotDashed /> In Progress
</Badge>
<Badge className="bg-primary/10 text-primary gap-2 dark:bg-primary/20">
<Check strokeWidth={2.5} /> Done
</Badge>
<Badge className="bg-destructive/10 text-destructive gap-2 dark:bg-destructive/20">
<X /> Cancelled
</Badge>
</div>
);
}
import { Badge } from "@/components/ui/badge";
import { Check, CircleDashed, CircleDotDashed, X } from "lucide-solid";
export default function BadgeStatus() {
return (
<div class="flex w-full justify-center flex-wrap gap-2">
<Badge variant="secondary" class="gap-2">
<CircleDashed /> Todo
</Badge>
<Badge class="bg-muted text-muted-foreground gap-2">
<CircleDotDashed /> In Progress
</Badge>
<Badge class="bg-primary/10 text-primary gap-2 dark:bg-primary/20">
<Check strokeWidth={2.5} /> Done
</Badge>
<Badge class="bg-destructive/10 text-destructive gap-2 dark:bg-destructive/20">
<X /> Cancelled
</Badge>
</div>
);
}
With Image
Badges can include images, such as avatars or profile pictures. Images are displayed as rounded circles and can be positioned before or after the label text.
import { Badge } from "@/components/ui/badge";
export default function BadgeImage() {
return (
<div className="flex w-full justify-center flex-wrap gap-2">
<Badge className="rounded-full ps-[3px]" variant="outline">
<img
src="https://github.com/shadcn.png"
className="h-5 w-5 rounded-full"
alt="shadcn"
height={20}
width={20}
/>
shadcn
</Badge>
<Badge className="rounded-full pe-[3px]" variant="outline">
shadcn
<img
src="https://github.com/shadcn.png"
className="h-5 w-5 rounded-full"
alt="shadcn"
height={20}
width={20}
/>
</Badge>
</div>
);
}
import { Badge } from "@/components/ui/badge";
export default function BadgeImage() {
return (
<div class="flex w-full justify-center flex-wrap gap-2">
<Badge class="rounded-full ps-[3px]" variant="outline">
<img
src="https://github.com/shadcn.png"
class="h-5 w-5 rounded-full"
alt="shadcn"
height={20}
width={20}
/>
shadcn
</Badge>
<Badge class="rounded-full pe-[3px]" variant="outline">
shadcn
<img
src="https://github.com/shadcn.png"
class="h-5 w-5 rounded-full"
alt="shadcn"
height={20}
width={20}
/>
</Badge>
</div>
);
}
Indicator on Buttons
Badges can be used as notification indicators on buttons. Position them absolutely to show counts, dots, or icons. Empty badges automatically shrink to a small dot.
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/badge";
import { AtSign, Bell, Mail, MessageCircle } from "lucide-react";
export default function BadgeIndicator() {
return (
<div className="flex w-full justify-center flex-wrap gap-6">
<Button size="md" variant="outline" className="relative">
<Bell />
<Badge
variant="destructive"
className="absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 h-5 min-w-5 p-0 px-0.5 rounded-full empty:h-2.5 empty:min-w-2.5"
/>
</Button>
<Button size="md" variant="outline" className="relative">
<Bell />
<Badge
variant="destructive"
className="absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 h-5 min-w-5 p-0 px-0.5 rounded-full empty:h-2.5 empty:min-w-2.5"
>
5
</Badge>
</Button>
<Button size="md" variant="outline" className="relative">
<Mail />
<Badge
variant="destructive"
className="absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 h-5 min-w-5 p-0 px-0.5 rounded-full empty:h-2.5 empty:min-w-2.5"
>
99+
</Badge>
</Button>
<Button size="md" variant="outline" className="relative">
<MessageCircle />
<Badge
variant="destructive"
className="absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 h-5 min-w-5 p-0 px-0.5 rounded-full empty:h-2.5 empty:min-w-2.5"
>
<AtSign className="size-3" />
</Badge>
</Button>
</div>
);
}
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/badge";
import { AtSign, Bell, Mail, MessageCircle } from "lucide-solid";
export default function BadgeIndicator() {
return (
<div class="flex w-full justify-center flex-wrap gap-6">
<Button size="md" variant="outline" class="relative">
<Bell />
<Badge
variant="destructive"
class="absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 h-5 min-w-5 p-0 px-0.5 rounded-full empty:h-2.5 empty:min-w-2.5"
/>
</Button>
<Button size="md" variant="outline" class="relative">
<Bell />
<Badge
variant="destructive"
class="absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 h-5 min-w-5 p-0 px-0.5 rounded-full empty:h-2.5 empty:min-w-2.5"
>
5
</Badge>
</Button>
<Button size="md" variant="outline" class="relative">
<Mail />
<Badge
variant="destructive"
class="absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 h-5 min-w-5 p-0 px-0.5 rounded-full empty:h-2.5 empty:min-w-2.5"
>
99+
</Badge>
</Button>
<Button size="md" variant="outline" class="relative">
<MessageCircle />
<Badge
variant="destructive"
class="absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 h-5 min-w-5 p-0 px-0.5 rounded-full empty:h-2.5 empty:min-w-2.5"
>
<AtSign class="size-3" />
</Badge>
</Button>
</div>
);
}