Lunia DS

Base components

Badge

A short label for a status, category or count in three styles and twelve colors, which eases its colors when its status changes and can carry a small remove button.

Loading preview

A grid of the three types in the twelve colors at size md, a row of sizes, badges with a dot, a flag, an avatar and an icon, and a status badge that changes from "In review" (warning) to "Approved" (success). Replay runs that status change again.

Installation

Run this in your app after the one-time setup on the Getting started page.

$ npx @ganisigit/lunia-ds add badge

Usage

The demo above, as code.

demo.tsx
'use client';

import * as React from 'react';
import { Badge } from './badge';
import type { BadgeColor, BadgeType } from './badge-variants';

const TYPES: BadgeType[] = ['pill-color', 'badge-color', 'badge-modern'];
const COLORS: BadgeColor[] = ['gray', 'brand', 'error', 'warning', 'success', 'blue'];

export default function BadgeDemo({ replay = 0 }: { replay?: number }) {
  const [approved, setApproved] = React.useState(false);
  const [tags, setTags] = React.useState(['Design', 'Research']);

  React.useEffect(() => {
    if (replay === 0) return;
    setApproved(false);
    setTags(['Design', 'Research']);
    const timer = window.setTimeout(() => setApproved(true), 600);
    return () => window.clearTimeout(timer);
  }, [replay]);

  return (
    <div className="flex flex-col items-center gap-5">
      {TYPES.map((type) => (
        <div key={type} className="flex flex-wrap justify-center gap-2">
          {COLORS.map((color) => (
            <Badge key={color} type={type} color={color} dot={type === 'badge-modern'}>
              Label
            </Badge>
          ))}
        </div>
      ))}
      <div className="flex flex-wrap items-center justify-center gap-2">
        <Badge data-testid="status" color={approved ? 'success' : 'warning'} dot>
          {approved ? 'Approved' : 'In review'}
        </Badge>
        {tags.map((tag) => (
          <Badge
            key={tag}
            type="badge-color"
            onRemove={() => setTags((list) => list.filter((t) => t !== tag))}
          >
            {tag}
          </Badge>
        ))}
      </div>
    </div>
  );
}

API

Parts: root, leading content (dot, country flag, avatar or icon), label, trailing icon, remove button (X close).

PropTypeDefaultDescription
size"sm" | "md" | "lg""md"22, 24 or 28px tall.
type"pill-color" | "badge-color" | "badge-modern""pill-color"Shape and color style: a round pill with a tinted fill, a rounded badge with a tinted fill, or a neutral rounded badge with a colored dot or icon.
color"gray" | "brand" | "error" | "warning" | "success" | "slate" | "sky" | "blue" | "indigo" | "purple" | "pink" | "orange""gray"Color set from the utility color tokens.
dotbooleanfalseShows a colored dot before the label.
iconOnlybooleanfalseShows only the icon passed in the default slot, as a square badge. Needs a label prop for screen readers.
labelstringundefinedAccessible name when iconOnly is set, and the name used in the remove button ("Remove Design").
removeLabelstring"Remove"Verb for the remove button name. The button is named removeLabel plus the badge text.
EventDescription
removeThe remove button was pressed. The remove button renders only when this event has a listener.
SlotDescription
defaultLabel text, or the icon when iconOnly is set.
leadingFlag, avatar or icon before the label (Figma Icon Country, Avatar or Icon leading).
trailingIcon after the label (Figma Icon trailing).

Motion

Every row names a token from the Motion page. The last column says what happens when the user asks for reduced motion.

IDTriggerElementPropertyFrom → toTimingReduced motion
BDG1color changes (for example a status moves from warning to success)root, dot, labelbackground-color, border-color, colorold color set → new color setduration-sm (200)ease-outSwaps at once
BDG2label text changeslabelopacityold text 1 -> 0 → new text 0 -> 1 (crossfade)duration-sm (200)ease-outSwaps at once
BDG3hover the remove buttonremove buttonbackground-color, icon colortransparent, color set icon → color set hover fill, color set icon hoverduration-sm (200)ease-outSwaps at once
BDG4press the remove buttonremove buttonscale1 → scale-pressduration-sm (200)ease-outStays still
BDG5keyboard focus on the remove buttonremove buttonbox-shadownone → focus ring (2px bg-primary gap, 4px focus-ring)duration-2xs (120)ease-outShows at once

Accessibility

  • The badge is a plain span with no role; it is read as part of the text around it.
  • The dot and leading icons are aria-hidden; when the color carries meaning (for example a status), the label says it in words.
  • iconOnly badges need the label prop, which becomes aria-label on a span with role img.
  • The remove button is a native button named removeLabel plus the badge text, for example "Remove Design".
  • The remove button is 16px; a transparent hit area grows the target to 24 x 24px (WCAG 2.5.8).
  • Reduced motion: colors and text swap at once and the remove button does not scale.