Badge
Compact label for status, counts, and tags. Eight 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.
Eight variants ship by default: default (filled primary), secondary, destructive, warning, success, outline, ghost, and link. The three status fills - destructive (error), warning (attention), success (ok) - each clear WCAG AA 4.5:1 in light and dark mode. 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> <Badge variant="warning">Warning</Badge> <Badge variant="success">Success</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>
<Badge variant="warning">Warning</Badge>
<Badge variant="success">Success</Badge>
</div>
);
}npx gremorie@latest add ng-badgePrerequisite
Angular components emit Tailwind utility classes and ship no compiled CSS. Without the setup below in src/styles.css the component mounts but renders completely unstyled. Requires Tailwind CSS v4.
@import 'tailwindcss';@import '@gremorie/tokens/theme.css';/* Required. Tailwind v4 skips node_modules by default, so without this line the components' utility classes never reach your compiled CSS and every Gremorie component renders unstyled. */@source '../node_modules/@gremorie';The @source line is required: Tailwind v4 skips node_modules by default. Adjust the relative path if your stylesheet is not at src/styles.css. Full setup in Installation.
import { 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>
<gr-badge variant="warning">Warning</gr-badge>
<gr-badge variant="success">Success</gr-badge>
</div>
`,
})
export class ExampleComponent {}API
<Badge>
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "secondary" | "destructive" | "warning" | "success" | "outline" | "ghost" | "link" | "default" | Visual style. default is filled primary; destructive / warning / success are the status fills (error, attention, ok); 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="warning">Warning</Badge> <Badge variant="success">Success</Badge> <Badge variant="outline">Outline</Badge> <Badge variant="ghost">Ghost</Badge> <Badge variant="link">Link</Badge> </div> );}All eight variants side by side: default (filled primary), secondary, destructive, warning, success, 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, AlertTriangleIcon, CheckIcon } from 'lucide-react';export function BadgeStatusPreview() { return ( <div className="flex flex-wrap items-center gap-2"> <Badge variant="success"> <CheckIcon /> Active </Badge> <Badge variant="warning"> <AlertTriangleIcon /> Expiring </Badge> <Badge variant="destructive"> <AlertCircleIcon /> Failed </Badge> <Badge variant="outline">Draft</Badge> </div> );}The three status fills map to the three outcomes: success for ok, warning for attention, destructive for error. Pair each one with an icon and 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).
- Status fill contrast:
destructive,warningandsuccessare token pairs (--warning/--warning-foreground) tuned so the text clears WCAG AA 4.5:1 at badge size in both light and dark mode. If you override the fill with a custom class, re-check the contrast yourself. - 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.