Skip to main content
Gremorie

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

DefaultSecondaryOutlineDestructive
'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 icon

Installation

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-badge
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>
    </div>
  `,
})
export class ExampleComponent {}

API

<Badge>

PropTypeDefaultDescription
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.
asChildbooleanfalseWhen true, renders the immediate child via Radix Slot and merges all classes onto it. Use to wrap an <a> or framework Link.
classNamestring-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:

  1. variant to switch the visual treatment.
  2. asChild to 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

DefaultSecondaryDestructiveOutlineGhostLink
'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

ActiveFailedDraft
'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.

'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

Inbox12Notifications99+
'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 styles aria-invalid={true} automatically with destructive border and ring - useful when Badge is part of a form field.
  • Card - frequent host for Badge via CardAction.
  • Avatar - pairs with AvatarBadge for presence dots.
  • Alert - for non-inline status that needs more room.

On this page