Propel

Badge

A small rounded pill of inline text for statuses, labels, and counts.

Badge
Show code
import { Badge } from "@makeplane/propel/components/badge";

export default function BasicDemo() {
  return <Badge label="Badge" variant="neutral" size="xs" />;
}

Installation

import { Badge } from "@makeplane/propel/components/badge";

Usage

import { Badge } from "@makeplane/propel/components/badge";

export default function BasicDemo() {
  return <Badge label="Badge" variant="neutral" size="xs" />;
}

Examples

Variants

The variant axis carries both semantic color (neutral, brand, info, success, warning, danger) and decorative label hues (grey, purple, indigo, emerald, yellow, crimson, orange). A semantic name says why the badge is coloured; a hue name says only what colour it is, for a badge that carries no meaning (“Live” is emerald, not success).

NeutralBrandInfoSuccessWarningDangerGreyPurpleIndigoEmeraldYellowCrimsonOrange
Show code
import { Badge, type BadgeVariant } from "@makeplane/propel/components/badge";

const VARIANTS: BadgeVariant[] = [
  // Semantic — the color carries meaning.
  "neutral",
  "brand",
  "info",
  "success",
  "warning",
  "danger",
  // Label hues — the color is decoration.
  "grey",
  "purple",
  "indigo",
  "emerald",
  "yellow",
  "crimson",
  "orange",
];

// Badge labels read as words: "Neutral", not "neutral".
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);

export default function VariantsDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      {VARIANTS.map((variant) => (
        <Badge key={variant} variant={variant} size="xs" label={capitalize(variant)} />
      ))}
    </div>
  );
}

Sizes

size has three steps — xs 20 px with 12px text, sm 24 px with 13px and md 28 px with 14px — following the Figma badges set: 6 / 6 / 8px sides, a 4 / 4 / 6px icon gap, a 14 / 14 / 16px glyph and a 4 / 6 / 6px corner. Height, gap, glyph and corner are the compact ladder’s rungs; the sides are a badge’s own, a step tighter than a button’s from sm up.

XSSMMD
Show code
import { Badge, type BadgeSize } from "@makeplane/propel/components/badge";

const SIZES: BadgeSize[] = ["xs", "sm", "md"];

export default function SizesDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      {SIZES.map((size) => (
        <Badge key={size} variant="brand" size={size} label={size.toUpperCase()} />
      ))}
    </div>
  );
}

With icon

DonePro
Show code
import { Badge } from "@makeplane/propel/components/badge";
import { Icon } from "@makeplane/propel/components/icon";
import { TickOutline, AiStar1Outline } from "@makeplane/propel/icons";

export default function WithIconDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Badge variant="success" size="xs" startIcon={<Icon icon={TickOutline} />} label="Done" />
      <Badge variant="brand" size="xs" endIcon={<Icon icon={AiStar1Outline} />} label="Pro" />
    </div>
  );
}

Icon only

Omit label for a compact icon-only badge: the label part is skipped so the icon sits centered, and the pill renders role="img". Because the icon is decorative, aria-label is required in this state — the type makes omitting both label and aria-label an error.

Show code
import { Badge, type BadgeSize } from "@makeplane/propel/components/badge";
import { Icon } from "@makeplane/propel/components/icon";
import { TickOutline } from "@makeplane/propel/icons";

const SIZES: BadgeSize[] = ["xs", "sm", "md"];

export default function IconOnlyDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      {SIZES.map((size) => (
        <Badge
          key={size}
          variant="success"
          size={size}
          startIcon={<Icon icon={TickOutline} />}
          aria-label="Completed"
        />
      ))}
    </div>
  );
}

API Reference

Badge

The ready-made badge: composes the atomic Badge pill with the BadgeLabel and optional leading (startIcon) and trailing (endIcon) icon slots. With no label the pill is icon-only — the label part is skipped entirely (an empty flex child would still consume the pill's gap and render it lopsided), and the pill defaults to role="img" so an author-supplied aria-label is valid ARIA (a bare <span>'s generic role doesn't support naming).

PropTypeDefaultDescription
size(required)"xs" | "sm" | "md"—
variant(required)"neutral" | "grey" | "brand" | "info" | "purple" | "indigo" | "success" | "emerald" | "warning" | "yellow" | "danger" | "crimson" | "orange"—
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.
startIconReactNode—Element rendered before the label (inline-start), e.g. <Icon icon={TickOutline} />.
endIconReactNode—Element rendered after the label (inline-end), e.g. <Icon icon={Sparkles} />.
labelstring—
aria-labelstring—