Badge
Compact label for status, counts, and tags. Six variants, optional asChild for link-style badges.
Overview
Badge is the static label primitive: a small, rounded pill for statuses ("Active", "Pending"), counts ("12", "99+"), tags ("AI", "Beta"), or category markers. It renders as a <span> by default - or any element via asChild - and is intentionally non-interactive. For selectable chips, reach for ToggleGroup; for clickable badges that link somewhere, use asChild to compose with an <a> or Link component.
Six variants ship by default: default (filled primary), secondary, destructive, outline, ghost, and link. Hover states only apply when Badge is rendered as an anchor (via the [a&]:hover:... selector pattern), so the static variants stay truly static.
Preview
'use client';import { Badge } from '@gremorie/rx-display';export function BadgePreview() { return ( <div className="flex flex-wrap items-center gap-2"> <Badge>Default</Badge> <Badge variant="secondary">Secondary</Badge> <Badge variant="outline">Outline</Badge> <Badge variant="destructive">Destructive</Badge> </div> );}Anatomy
Badge rounded, bordered pill wrapping text and/or a leading size-3 iconInstallation
bash npx gremorie@latest add rx-badge bash pnpm dlx gremorie@latest add rx-badge bash yarn dlx gremorie@latest add rx-badge bash bunx --bun gremorie@latest add rx-badge Usage
import { Badge } from "@gremorie/rx-display";
export function Example() {
return (
<div className="flex flex-wrap items-center gap-2">
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="destructive">Destructive</Badge>
</div>
);
}npx gremorie@latest add ng-badgeimport { Component } from '@angular/core';
import { Badge } from '@gremorie/ng-display';
@Component({
selector: 'app-example',
standalone: true,
imports: [Badge],
template: `
<div class="flex flex-wrap items-center gap-2">
<gr-badge>Default</gr-badge>
<gr-badge variant="secondary">Secondary</gr-badge>
<gr-badge variant="outline">Outline</gr-badge>
<gr-badge variant="destructive">Destructive</gr-badge>
</div>
`,
})
export class ExampleComponent {}API
<Badge>
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "secondary" | "destructive" | "outline" | "ghost" | "link" | "default" | Visual style. default is filled primary; outline shows only a border; ghost is invisible until rendered as an anchor; link styles as inline text. |
asChild | boolean | false | When true, renders the immediate child via Radix Slot and merges all classes onto it. Use to wrap an <a> or framework Link. |
className | string | - | Extra classes merged via cn. |
All other span props are forwarded.
The badgeVariants CVA function is also exported, so you can compose Badge styles onto a custom element without using Badge itself:
import { badgeVariants } from '@gremorie/rx-display';
<a href="/docs" className={badgeVariants({ variant: 'outline' })}>
Docs
</a>;Composition
Badge is a leaf component - it has no sub-parts. The two composition levers are:
variantto switch the visual treatment.asChildto render Badge styles onto a different element (typically an anchor or framework link).
Icons compose naturally: drop an SVG inside Badge and the variant CSS handles sizing ([&>svg]:size-3).
Variations
All variants
'use client';import { Badge } from '@gremorie/rx-display';export function BadgeVariantsPreview() { return ( <div className="flex flex-wrap items-center gap-2"> <Badge>Default</Badge> <Badge variant="secondary">Secondary</Badge> <Badge variant="destructive">Destructive</Badge> <Badge variant="outline">Outline</Badge> <Badge variant="ghost">Ghost</Badge> <Badge variant="link">Link</Badge> </div> );}All six variants side by side: default (filled primary), secondary, destructive, outline, ghost, and link. ghost reads as plain text until rendered as an anchor.
Status indicator
'use client';import { Badge } from '@gremorie/rx-display';import { AlertCircleIcon, CheckIcon } from 'lucide-react';export function BadgeStatusPreview() { return ( <div className="flex flex-wrap items-center gap-2"> <Badge variant="secondary"> <CheckIcon /> Active </Badge> <Badge variant="destructive"> <AlertCircleIcon /> Failed </Badge> <Badge variant="outline">Draft</Badge> </div> );}Pair an icon with text so the status reads clearly without relying on color alone. Icons inside Badge size automatically via [&>svg]:size-3.
Badge as a link (asChild)
'use client';import { Badge } from '@gremorie/rx-display';export function BadgeLinkPreview() { return ( <Badge asChild variant="outline"> <a href="#docs">Read the docs</a> </Badge> );}Hover styles activate automatically when Badge renders as an anchor - that is what the [a&]:hover:... selectors do internally.
Count badge
'use client';import { Badge } from '@gremorie/rx-display';export function BadgeCountPreview() { return ( <div className="flex items-center gap-2"> <span className="text-sm">Inbox</span> <Badge>12</Badge> <span className="text-sm">Notifications</span> <Badge variant="destructive">99+</Badge> </div> );}Accessibility
- Static by default: Badge renders a
<span>and has no role. Screen readers announce its text content as inline text. - Status updates: when a Badge's content changes dynamically (e.g. a count that updates live), wrap a parent in an
aria-live="polite"region so assistive tech announces the change. - Color-only meaning is not enough: use icons or text in addition to the variant color so users who can't perceive color still get the message ("Active" plus a check icon, not just a green pill).
- Interactive badges: when rendering as an anchor via
asChild, the anchor's native semantics apply - focus, Enter to activate, screen reader announcement as a link. aria-invalid: Badge stylesaria-invalid={true}automatically with destructive border and ring - useful when Badge is part of a form field.