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
| Prop | Type | Default | What 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. |
dot | boolean | false | Shows a small leading dot in the tone colour. |
...rest | ComponentProps<"span"> | none | Every native span attribute, including title and id. |
Anatomy
| root | .eq-badge | A span, 20px high. |
|---|---|---|
| dot | .eq-badge-dot | Only with dot. Hidden from assistive tech. |
| label | .eq-badge-label | Truncates 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;
}
}