Badge

A compact marker for status, counts and categories.

Active

Installation

Terminal
pnpm dlx @dowel-ui/cli add badge

npm packages installed: class-variance-authority, radix-ui.

Accessibility

The badge label must convey the meaning on its own; variant colour is decoration. Use asChild to render an interactive badge as a link or button rather than adding handlers.

Props

Badge

PropTypeDefault
size"sm" | "md""md"
variant"default" | "secondary" | "outline" | "destructive" | "success" | "warning" | "info""default"
asChildbooleanfalse

Plus every attribute of <span>.

Quality

7/7 checks, measured from the source and its tests

  • Testedpasses
  • axe assertionpasses
  • Keyboard testeddoes not apply
  • Storybook examplespasses
  • Accessibility documentedpasses
  • Semantic tokens onlypasses
  • Motion from tokensdoes not apply
  • className mergedpasses
  • Visible focusdoes not apply
  • No fixed widthspasses

Used in

Whole screens assembled from this component. Installing one brings this and everything else it needs with it.

Source

This is exactly what dowel add badge writes into your project, with imports rewritten to your own path alias.

ui/badge.tsx
import { cva, type VariantProps } from "class-variance-authority";
import { Slot } from "radix-ui";
import type { ComponentPropsWithRef } from "react";

import { focusRing, iconSlot } from "@/lib/styles";
import { cn } from "@/lib/utils";

const badgeVariants = cva(
  cn(
    "inline-flex w-fit shrink-0 items-center justify-center gap-1 rounded-full border font-medium whitespace-nowrap",
    "[&_svg:not([class*='size-'])]:size-3",
    focusRing,
    iconSlot,
  ),
  {
    variants: {
      variant: {
        default: "border-transparent bg-primary text-primary-foreground",
        secondary: "border-transparent bg-secondary text-secondary-foreground",
        outline: "border-border-strong bg-transparent text-foreground",
        destructive: "border-transparent bg-destructive text-destructive-foreground",
        success: "border-transparent bg-success text-success-foreground",
        warning: "border-transparent bg-warning text-warning-foreground",
        info: "border-transparent bg-info text-info-foreground",
      },
      size: {
        sm: "h-5 px-1.5 text-2xs",
        md: "h-6 px-2 text-xs",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "md",
    },
  },
);

export interface BadgeProps
  extends ComponentPropsWithRef<"span">, VariantProps<typeof badgeVariants> {
  asChild?: boolean;
}

/**
 * Compact status or category marker.
 *
 * Colour alone never carries the meaning: the label text must say what the
 * badge means, so the monochrome theme and colour-blind users lose nothing.
 */
export function Badge({ className, variant, size, asChild = false, ...props }: BadgeProps) {
  const Comp = asChild ? Slot.Root : "span";
  return <Comp className={cn(badgeVariants({ variant, size }), className)} {...props} />;
}

export { badgeVariants };