Display

Badge

A small status label for a row, card or heading. Four tones, soft or solid, with an optional dot.

Built on
<span>
Client JavaScript
None
Works without React
Yes, as HTML and CSS
Source, gzipped
1.2 kB
Dependencies
React
LiveBuildingFailedDraft
Try its options

Loading its options.

Code
import { Badge } from "@/components/equip/badge";

export default function Demo() {
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 6, justifyContent: "center", maxWidth: 200 }}>
      <Badge tone="ok" dot>Live</Badge>
      <Badge tone="warn" dot>Building</Badge>
      <Badge tone="danger" variant="solid">Failed</Badge>
      <Badge>Draft</Badge>
    </div>
  );
}
Your versionchange its options in Look, This part
HTMLworks in any framework, with the part's CSS
<div style="display:flex;flex-wrap:wrap;gap:6px;justify-content:center;max-width:200px">
  <span class="eq-badge" data-tone="ok" data-variant="soft" data-dot="true">
    <span class="eq-badge-dot" aria-hidden="true"></span>
    <span class="eq-badge-label">Live</span>
  </span>
  <span class="eq-badge" data-tone="warn" data-variant="soft" data-dot="true">
    <span class="eq-badge-dot" aria-hidden="true"></span>
    <span class="eq-badge-label">Building</span>
  </span>
  <span class="eq-badge" data-tone="danger" data-variant="solid">
    <span class="eq-badge-label">Failed</span>
  </span>
  <span class="eq-badge" data-tone="neutral" data-variant="soft">
    <span class="eq-badge-label">Draft</span>
  </span>
</div>

Examples

Tones

Every label names its status in words, so the meaning survives without colour.

QueuedHealthyDegradedDown
Code
import { Badge } from "@/components/equip/badge";

export default function Example() {
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
      <Badge>Queued</Badge>
      <Badge tone="ok">Healthy</Badge>
      <Badge tone="warn">Degraded</Badge>
      <Badge tone="danger">Down</Badge>
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="display:flex;flex-wrap:wrap;gap:8px">
  <span class="eq-badge" data-tone="neutral" data-variant="soft">
    <span class="eq-badge-label">Queued</span>
  </span>
  <span class="eq-badge" data-tone="ok" data-variant="soft">
    <span class="eq-badge-label">Healthy</span>
  </span>
  <span class="eq-badge" data-tone="warn" data-variant="soft">
    <span class="eq-badge-label">Degraded</span>
  </span>
  <span class="eq-badge" data-tone="danger" data-variant="soft">
    <span class="eq-badge-label">Down</span>
  </span>
</div>

Solid and dot

Keep solid for the one status that needs action; the dot adds a quieter cue to a soft label.

ArchivedPaidPast dueCard declined
PausedOnlineRate limitedOffline
Code
import { Badge } from "@/components/equip/badge";

export default function Example() {
  return (
    <div style={{ display: "grid", gap: 8, justifyItems: "start" }}>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
        <Badge variant="solid">Archived</Badge>
        <Badge tone="ok" variant="solid">Paid</Badge>
        <Badge tone="warn" variant="solid">Past due</Badge>
        <Badge tone="danger" variant="solid">Card declined</Badge>
      </div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
        <Badge dot>Paused</Badge>
        <Badge tone="ok" dot>Online</Badge>
        <Badge tone="warn" dot>Rate limited</Badge>
        <Badge tone="danger" dot>Offline</Badge>
      </div>
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:8px;justify-items:start">
  <div style="display:flex;flex-wrap:wrap;gap:8px">
    <span class="eq-badge" data-tone="neutral" data-variant="solid">
      <span class="eq-badge-label">Archived</span>
    </span>
    <span class="eq-badge" data-tone="ok" data-variant="solid">
      <span class="eq-badge-label">Paid</span>
    </span>
    <span class="eq-badge" data-tone="warn" data-variant="solid">
      <span class="eq-badge-label">Past due</span>
    </span>
    <span class="eq-badge" data-tone="danger" data-variant="solid">
      <span class="eq-badge-label">Card declined</span>
    </span>
  </div>
  <div style="display:flex;flex-wrap:wrap;gap:8px">
    <span class="eq-badge" data-tone="neutral" data-variant="soft" data-dot="true">
      <span class="eq-badge-dot" aria-hidden="true"></span>
      <span class="eq-badge-label">Paused</span>
    </span>
    <span class="eq-badge" data-tone="ok" data-variant="soft" data-dot="true">
      <span class="eq-badge-dot" aria-hidden="true"></span>
      <span class="eq-badge-label">Online</span>
    </span>
    <span class="eq-badge" data-tone="warn" data-variant="soft" data-dot="true">
      <span class="eq-badge-dot" aria-hidden="true"></span>
      <span class="eq-badge-label">Rate limited</span>
    </span>
    <span class="eq-badge" data-tone="danger" data-variant="soft" data-dot="true">
      <span class="eq-badge-dot" aria-hidden="true"></span>
      <span class="eq-badge-label">Offline</span>
    </span>
  </div>
</div>

In a deploy list

At 20px high a badge fits inside a 28px row without changing its height.

  • checkout-apieu-west-1Live
  • search-indexerus-east-2Building
  • billing-workerap-south-1Failed
Code
import { Badge } from "@/components/equip/badge";

const deploys = [
  { name: "checkout-api", region: "eu-west-1", status: "Live", tone: "ok" },
  { name: "search-indexer", region: "us-east-2", status: "Building", tone: "warn" },
  { name: "billing-worker", region: "ap-south-1", status: "Failed", tone: "danger" },
] as const;

export default function Example() {
  return (
    <ul style={{ display: "grid", gap: 4, width: "100%", maxWidth: 280, margin: 0, padding: 0, listStyle: "none", fontSize: 13 }}>
      {deploys.map((d) => (
        <li key={d.name} style={{ display: "flex", alignItems: "center", gap: 8, height: 28 }}>
          <span style={{ fontWeight: 650 }}>{d.name}</span>
          <span style={{ color: "var(--eq-ink-2)" }}>{d.region}</span>
          <Badge tone={d.tone} dot style={{ marginInlineStart: "auto" }}>
            {d.status}
          </Badge>
        </li>
      ))}
    </ul>
  );
}
HTMLworks in any framework, with the part's CSS
<ul style="display:grid;gap:4px;width:100%;max-width:280px;margin:0;padding:0;list-style:none;font-size:13px">
  <li style="display:flex;align-items:center;gap:8px;height:28px">
    <span style="font-weight:650">checkout-api</span>
    <span style="color:var(--eq-ink-2)">eu-west-1</span>
    <span style="margin-inline-start:auto" class="eq-badge" data-tone="ok" data-variant="soft" data-dot="true">
      <span class="eq-badge-dot" aria-hidden="true"></span>
      <span class="eq-badge-label">Live</span>
    </span>
  </li>
  <li style="display:flex;align-items:center;gap:8px;height:28px">
    <span style="font-weight:650">search-indexer</span>
    <span style="color:var(--eq-ink-2)">us-east-2</span>
    <span style="margin-inline-start:auto" class="eq-badge" data-tone="warn" data-variant="soft" data-dot="true">
      <span class="eq-badge-dot" aria-hidden="true"></span>
      <span class="eq-badge-label">Building</span>
    </span>
  </li>
  <li style="display:flex;align-items:center;gap:8px;height:28px">
    <span style="font-weight:650">billing-worker</span>
    <span style="color:var(--eq-ink-2)">ap-south-1</span>
    <span style="margin-inline-start:auto" class="eq-badge" data-tone="danger" data-variant="soft" data-dot="true">
      <span class="eq-badge-dot" aria-hidden="true"></span>
      <span class="eq-badge-label">Failed</span>
    </span>
  </li>
</ul>

Props

PropTypeDefaultWhat it does
tone"neutral" | "ok" | "warn" | "danger""neutral"What the status means. The text has to say it too.
variant"soft" | "solid""soft"Soft is a tinted label. Solid is filled, for the one status that must be seen first.
dotbooleanfalseShows a small leading dot in the tone colour.
...restComponentProps<"span">noneEvery native span attribute, including title and id.

Anatomy

root.eq-badgeA span, 20px high.
dot.eq-badge-dotOnly with dot. Hidden from assistive tech.
label.eq-badge-labelTruncates with an ellipsis if the badge is squeezed.

Notes

  • Tone is colour, and colour alone does not reach everyone, so the label must name the status: write Failed, not just a red badge.
  • A badge is plain text with no role; if it changes while the page is open, put it inside a role="status" region so the change is announced.
  • Solid is louder than soft. Use it for one status per view, usually the one that needs action.
  • Not interactive. For a removable filter or a toggle, use a button instead.

Source

Two files, and they are the whole part. Open either to read it, copy it straight from its bar, or add the part to your loadout and install it with everything else you picked.

components/equip/badge.tsx33 lines
import type { ComponentProps } from "react";
import "./badge.css";

export type BadgeProps = ComponentProps<"span"> & {
  /** Meaning of the status. The text must still say it: colour alone is not enough. */
  tone?: "neutral" | "ok" | "warn" | "danger";
  /** `soft` is a tinted label, `solid` a filled one for the status that must be seen first. */
  variant?: "soft" | "solid";
  /** Shows a small leading dot in the tone colour. */
  dot?: boolean;
};

export function Badge({
  tone = "neutral",
  variant = "soft",
  dot = false,
  className,
  children,
  ...rest
}: BadgeProps) {
  return (
    <span
      {...rest}
      className={["eq-badge", className].filter(Boolean).join(" ")}
      data-tone={tone}
      data-variant={variant}
      data-dot={dot || undefined}
    >
      {dot && <span className="eq-badge-dot" aria-hidden="true" />}
      <span className="eq-badge-label">{children}</span>
    </span>
  );
}
components/equip/badge.css79 lines
@layer equip {
  .eq-badge {
    --_tone: var(--eq-ink-2);
    --_bg: var(--eq-tint);
    --_ink: var(--eq-ink);
    --_line: var(--eq-line);

    box-sizing: border-box;
    display: inline-flex;
    flex: none;
    align-items: center;
    gap: var(--eq-space-1);
    height: 20px;
    /* 1px border plus 7px padding puts the text 8px from the outer edge. */
    padding-inline: calc(var(--eq-space-2) - 1px);
    border: 1px solid var(--_line);
    border-radius: var(--eq-radius-s);
    background: var(--_bg);
    color: var(--_ink);
    font: var(--eq-weight-strong) var(--eq-text-xs) / 1 var(--eq-font);
    letter-spacing: 0.005em;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    vertical-align: middle;
  }

  /* A round dot reads as further in than a letter, so the leading side comes in by 2px. */
  .eq-badge[data-dot] {
    padding-inline-start: calc(var(--eq-space-1) + 1px);
  }

  .eq-badge[data-tone="ok"] {
    --_tone: var(--eq-ok);
  }

  .eq-badge[data-tone="warn"] {
    --_tone: var(--eq-warn);
  }

  .eq-badge[data-tone="danger"] {
    --_tone: var(--eq-danger);
  }

  .eq-badge[data-variant="soft"]:not([data-tone="neutral"]) {
    --_bg: color-mix(in srgb, var(--_tone) 11%, transparent);
    --_ink: var(--_tone);
    --_line: color-mix(in srgb, var(--_tone) 28%, transparent);
  }

  .eq-badge[data-variant="solid"] {
    --_bg: var(--_tone);
    --_ink: var(--eq-solid-ink);
    --_line: transparent;
  }

  .eq-badge[data-variant="solid"][data-tone="neutral"] {
    --_bg: var(--eq-solid);
  }

  .eq-badge-dot {
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: var(--eq-radius-full);
    background: var(--_tone);
  }

  .eq-badge[data-variant="solid"] .eq-badge-dot {
    background: currentColor;
  }

  .eq-badge-label {
    min-width: 0;
    /* Room for descenders, which overflow: hidden would otherwise clip at line-height 1. */
    line-height: 1.25;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

EqUIp is in beta: free for non-commercial use, no warranty, and things can change. Terms

Nothing picked yet. Press A on a part, or use the plus on the board, and the install command builds here.

Loading the index.

Loading the index