API
Badge
A small label for status, counts, and categories.
Installation
pnpm dlx zazz-ui add badgenpx zazz-ui add badgeyarn dlx zazz-ui add badgebunx zazz-ui add badgeAlso adds fields, which it depends on. Run zazz-ui init first if the project has no zazz/ folder yet.
Examples
Default
<div
style="
--display: flex;
--flex-wrap: wrap;
--items: center;
--gap: 4;
"
>
<button type="button" data-ui="badge">Category</button>
<button type="button" data-ui="badge" data-badge-variant="primary">New</button>
<button type="button" data-ui="badge" data-badge-variant="muted">Draft</button>
<button type="button" data-ui="badge" data-badge-variant="ghost">Inline</button>
<button type="button" data-ui="badge" data-badge-variant="info">Update</button>
<button type="button" data-ui="badge" data-badge-variant="success">Paid</button>
<button type="button" data-ui="badge" data-badge-variant="warning">Pending</button>
<button type="button" data-ui="badge" data-badge-variant="destructive">Overdue</button>
<button type="button" data-ui="badge" data-badge-variant="link">Posts</button>
</div>/**
* badge.css: Badge ([data-ui~="badge"])
*
* @layer variables, components
* @requires _layers.css, _variables.css, _reset.css, _properties.css (--_*-resolved, --_focus-shadow), fields.css (--ui-field-* border/ring)
* @uses color-mix(), oklch(from …): variant hover/active tints
* @uses text-box: vertical trim (patchy browser support)
* @tokens --ui-badge-* (@layer variables; hooks are named after the utilities they back)
* @consumedby combobox.css (selection tags)
*
* Every utility-backed declaration reads one chain, highest first:
* var(--_<utility>-resolved, var(--_badge-<utility>, var(--ui-badge-<utility>)))
* States apply only to interactive badges (button, a[href]).
*/
@layer variables {
:root {
/* border + ring follow --ui-field-*; metrics stay independent (badges are smaller) */
--ui-badge-ring-color: var(--ui-field-ring-color);
--ui-badge-font-family: var(--font-family-body);
--ui-badge-font-size: var(--font-size-xs);
--ui-badge-font-weight: var(--font-weight-strong);
--ui-badge-leading: 1;
--ui-badge-bg: var(--color-background);
--ui-badge-bg--hover: var(--color-muted);
--ui-badge-bg--active: oklch(from var(--color-muted) l c h / calc(alpha - 0.02));
--ui-badge-text: var(--color-foreground);
--ui-badge-text--hover: var(--color-foreground);
--ui-badge-text--active: var(--color-foreground);
--ui-badge-border-width: var(--ui-field-border-width);
--ui-badge-border-style: var(--ui-field-border-style);
--ui-badge-border-color: var(--ui-field-border-color);
--ui-badge-border-color--hover: var(--ui-field-border-color);
--ui-badge-border-color--active: var(--ui-field-border-color);
/* dual-mode: a scale number or a length */
--ui-badge-gap: var(--spacing);
--ui-badge-px: calc(var(--spacing) * 1.25);
--ui-badge-h: calc(var(--spacing) * 5);
--ui-badge-rounded: var(--radius-sm);
--ui-badge-icon-size: calc(var(--spacing) * 3);
/* never `none`: the focus ring layers into this box-shadow list */
--ui-badge-shadow: 0 0 transparent;
}
}
/* Variant privates: non-inheriting so a variant never cascades into a nested badge. */
@property --_badge-ring-color {
syntax: "*";
inherits: false;
}
@property --_badge-font-family {
syntax: "*";
inherits: false;
}
@property --_badge-font-size {
syntax: "*";
inherits: false;
}
@property --_badge-font-weight {
syntax: "*";
inherits: false;
}
@property --_badge-leading {
syntax: "*";
inherits: false;
}
@property --_badge-bg {
syntax: "*";
inherits: false;
}
@property --_badge-bg--hover {
syntax: "*";
inherits: false;
}
@property --_badge-bg--active {
syntax: "*";
inherits: false;
}
@property --_badge-text {
syntax: "*";
inherits: false;
}
@property --_badge-text--hover {
syntax: "*";
inherits: false;
}
@property --_badge-text--active {
syntax: "*";
inherits: false;
}
@property --_badge-border-width {
syntax: "*";
inherits: false;
}
@property --_badge-border-style {
syntax: "*";
inherits: false;
}
@property --_badge-border-color {
syntax: "*";
inherits: false;
}
@property --_badge-border-color--hover {
syntax: "*";
inherits: false;
}
@property --_badge-border-color--active {
syntax: "*";
inherits: false;
}
@property --_badge-gap {
syntax: "*";
inherits: false;
}
@property --_badge-px {
syntax: "*";
inherits: false;
}
@property --_badge-h {
syntax: "*";
inherits: false;
}
@property --_badge-rounded {
syntax: "*";
inherits: false;
}
@property --_badge-icon-size {
syntax: "*";
inherits: false;
}
@property --_badge-shadow {
syntax: "*";
inherits: false;
}
@layer ui.components {
:where([data-ui~="badge"]) {
font-family: var(
--_font-family-resolved,
var(--_badge-font-family, var(--ui-badge-font-family))
);
font-size: var(--_font-size-resolved, var(--_badge-font-size, var(--ui-badge-font-size)));
font-weight: var(
--_font-weight-resolved,
var(--_badge-font-weight, var(--ui-badge-font-weight))
);
line-height: var(--_leading-resolved, var(--_badge-leading, var(--ui-badge-leading)));
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
--_gap-len: var(--_gap-resolved, var(--_badge-gap, var(--ui-badge-gap)));
--_gap-num: var(--_gap-resolved, var(--_badge-gap, var(--ui-badge-gap)));
gap: calc(var(--_gap-len) + var(--_gap-num) * var(--spacing));
--_h-len: var(--_h-resolved, var(--_badge-h, var(--ui-badge-h)));
--_h-num: var(--_h-resolved, var(--_badge-h, var(--ui-badge-h)));
block-size: calc(var(--_h-len) + var(--_h-num) * var(--spacing));
min-inline-size: max-content;
--_px-len: var(--_px-resolved, var(--_badge-px, var(--ui-badge-px)));
--_px-num: var(--_px-resolved, var(--_badge-px, var(--ui-badge-px)));
padding-inline: calc(var(--_px-len) + var(--_px-num) * var(--spacing));
border-width: var(
--_border-width-resolved,
var(--_badge-border-width, var(--ui-badge-border-width))
);
border-style: var(
--_border-style-resolved,
var(--_badge-border-style, var(--ui-badge-border-style))
);
border-color: var(
--_border-color-resolved,
var(--_badge-border-color, var(--ui-badge-border-color))
);
border-radius: var(--_rounded-resolved, var(--_badge-rounded, var(--ui-badge-rounded)));
color: var(--_text-resolved, var(--_badge-text, var(--ui-badge-text)));
background-color: var(--_bg-resolved, var(--_badge-bg, var(--ui-badge-bg)));
background-clip: padding-box;
white-space-collapse: collapse;
/* The ring is published to --_focus-shadow so the utilities layer's
box-shadow emission keeps it above a --shadow or --ring utility; the ring
widths grow from 0 on :focus-visible so the ring eases in. */
--_ring-offset-width: 0px;
--_ring-width: 0px;
--_focus-in: max(0, -1 * sign(var(--_focus-ring-w, 1px)));
--_ring-fill: color-mix(
in oklch,
var(--_ring-color-resolved, var(--_badge-ring-color, var(--ui-badge-ring-color)))
var(--ring-opacity),
transparent
);
--_focus-shadow:
0 0 0 calc(var(--_ring-offset-width) * (1 - var(--_focus-in))) var(--ring-offset-shadow-color),
0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * (1 - var(--_focus-in)))
var(--_ring-fill, var(--ring-shadow-color)),
inset 0 0 0 calc(var(--_ring-offset-width) * var(--_focus-in)) var(--ring-offset-shadow-color),
inset 0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * var(--_focus-in))
var(--_ring-fill, var(--ring-shadow-color));
box-shadow:
var(--_focus-shadow, 0 0 #0000),
var(--_shadow-resolved, var(--_badge-shadow, var(--ui-badge-shadow)));
cursor: default;
text-decoration: none;
/* transparent twin of the ring for forced-colors / high-contrast modes */
outline: abs(var(--_focus-ring-w, var(--outline-width))) var(--outline-style) transparent;
outline-offset: calc(
var(--outline-offset) - var(--_focus-in) *
(2 * var(--outline-offset) + abs(var(--_focus-ring-w, var(--outline-width))))
);
transition: var(--default-transition);
user-select: none;
text-box: trim-both ex alphabetic;
}
a[href] :where([data-ui~="badge"]):not(:disabled, [aria-disabled="true"]),
a[href]:where([data-ui~="badge"]):not(:disabled, [aria-disabled="true"]) {
cursor: pointer;
}
/* ===========================================================================
BADGE STATES — interactive badges only (button, a[href])
=========================================================================== */
@media (hover: hover) {
:where([data-ui~="badge"]):is(button, a[href]):hover:not(:disabled, [aria-disabled="true"]) {
color: var(--_text-resolved, var(--_badge-text--hover, var(--ui-badge-text--hover)));
background-color: var(--_bg-resolved, var(--_badge-bg--hover, var(--ui-badge-bg--hover)));
border-color: var(
--_border-color-resolved,
var(--_badge-border-color--hover, var(--ui-badge-border-color--hover))
);
}
}
:where([data-ui~="badge"]):is(button, a[href]):active:not(:disabled, [aria-disabled="true"]) {
color: var(--_text-resolved, var(--_badge-text--active, var(--ui-badge-text--active)));
background-color: var(--_bg-resolved, var(--_badge-bg--active, var(--ui-badge-bg--active)));
border-color: var(
--_border-color-resolved,
var(--_badge-border-color--active, var(--ui-badge-border-color--active))
);
}
:where([data-ui~="badge"]):is(button, a[href]):focus-visible:not(
:disabled,
[aria-disabled="true"]
) {
--_ring-offset-width: var(--ring-offset-width);
--_ring-width: abs(var(--_focus-ring-w, var(--ring-width)));
outline-color: transparent;
}
:where([data-ui~="badge"]):is(button, a[href])[aria-busy="true"] {
cursor: progress;
}
/* ===========================================================================
BADGE ICONS
=========================================================================== */
:where([data-ui~="badge"]) > svg {
inline-size: var(--ui-badge-icon-size);
block-size: var(--ui-badge-icon-size);
pointer-events: none;
justify-content: center;
align-items: center;
display: flex;
padding: 0;
}
/* ===========================================================================
BADGE VARIANTS — data-badge-variant writes the privates
=========================================================================== */
:where([data-ui~="badge"][data-badge-variant="primary"]) {
--_badge-bg: var(--color-primary);
--_badge-bg--hover: oklch(from var(--color-primary) l c h / 0.9);
--_badge-bg--active: oklch(from var(--color-primary) l c h / 0.8);
--_badge-text: var(--color-primary-foreground);
--_badge-text--hover: var(--color-primary-foreground);
--_badge-text--active: var(--color-primary-foreground);
--_badge-border-color: transparent;
--_badge-border-color--hover: transparent;
--_badge-border-color--active: transparent;
}
:where([data-ui~="badge"][data-badge-variant="secondary"]) {
--_badge-bg: var(--color-secondary);
--_badge-bg--hover: oklch(from var(--color-secondary) l c h / 0.9);
--_badge-bg--active: oklch(from var(--color-secondary) l c h / 0.8);
--_badge-text: var(--color-secondary-foreground);
--_badge-text--hover: var(--color-secondary-foreground);
--_badge-text--active: var(--color-secondary-foreground);
--_badge-border-color: transparent;
--_badge-border-color--hover: transparent;
--_badge-border-color--active: transparent;
}
:where([data-ui~="badge"][data-badge-variant="tertiary"]) {
--_badge-bg: var(--color-tertiary);
--_badge-bg--hover: oklch(from var(--color-tertiary) l c h / 0.9);
--_badge-bg--active: oklch(from var(--color-tertiary) l c h / 0.8);
--_badge-text: var(--color-tertiary-foreground);
--_badge-text--hover: var(--color-tertiary-foreground);
--_badge-text--active: var(--color-tertiary-foreground);
--_badge-border-color: transparent;
--_badge-border-color--hover: transparent;
--_badge-border-color--active: transparent;
}
:where([data-ui~="badge"][data-badge-variant="muted"]) {
--_badge-bg: var(--color-muted);
--_badge-bg--hover: oklch(from var(--color-muted) l c h / calc(alpha - 0.02));
--_badge-bg--active: var(--color-muted);
--_badge-text: var(--color-foreground);
--_badge-text--hover: var(--color-foreground);
--_badge-text--active: var(--color-foreground);
--_badge-border-color: transparent;
--_badge-border-color--hover: transparent;
--_badge-border-color--active: transparent;
}
:where([data-ui~="badge"][data-badge-variant="ghost"]) {
--_badge-bg: transparent;
--_badge-bg--hover: var(--color-muted);
--_badge-bg--active: oklch(from var(--color-muted) l c h / calc(alpha - 0.02));
--_badge-text: var(--color-foreground);
--_badge-text--hover: var(--color-foreground);
--_badge-text--active: var(--color-foreground);
--_badge-border-color: transparent;
--_badge-border-color--hover: transparent;
--_badge-border-color--active: transparent;
}
/* Status variants (info, success, warning, destructive) share one soft
design: the status color at 10% alpha (20% in dark) as the surface, and
the status hue as the text with its lightness clamped (at most 0.45 in
light, at least 0.82 in dark) so it holds 4.5:1 on the tint and on the
hover and active steps; status-contrast.browser.test.ts checks it. No border. One rule derives the states from the
surface; each variant names only its colors. */
:where(
[data-ui~="badge"]:is(
[data-badge-variant="info"],
[data-badge-variant="success"],
[data-badge-variant="warning"],
[data-badge-variant="destructive"]
)
) {
--_badge-bg--hover: oklch(from var(--_badge-bg) l c h / calc(alpha + 0.1));
--_badge-bg--active: oklch(from var(--_badge-bg) l c h / calc(alpha + 0.15));
--_badge-text--hover: var(--_badge-text);
--_badge-text--active: var(--_badge-text);
--_badge-border-color: transparent;
--_badge-border-color--hover: transparent;
--_badge-border-color--active: transparent;
--_badge-shadow: 0 0 transparent;
}
:where([data-ui~="badge"][data-badge-variant="info"]) {
--_badge-ring-color: var(--color-info);
--_badge-bg: light-dark(
oklch(from var(--color-info) l c h / 0.1),
oklch(from var(--color-info) l c h / 0.2)
);
--_badge-text: light-dark(
oklch(from var(--color-info) min(l, 0.45) c h),
oklch(from var(--color-info) max(l, 0.82) c h)
);
}
:where([data-ui~="badge"][data-badge-variant="success"]) {
--_badge-ring-color: var(--color-success);
--_badge-bg: light-dark(
oklch(from var(--color-success) l c h / 0.1),
oklch(from var(--color-success) l c h / 0.2)
);
--_badge-text: light-dark(
oklch(from var(--color-success) min(l, 0.45) c h),
oklch(from var(--color-success) max(l, 0.82) c h)
);
}
:where([data-ui~="badge"][data-badge-variant="warning"]) {
--_badge-ring-color: var(--color-warning);
--_badge-bg: light-dark(
oklch(from var(--color-warning) l c h / 0.1),
oklch(from var(--color-warning) l c h / 0.2)
);
--_badge-text: light-dark(
oklch(from var(--color-warning) min(l, 0.45) c h),
oklch(from var(--color-warning) max(l, 0.82) c h)
);
}
:where([data-ui~="badge"][data-badge-variant="destructive"]) {
--_badge-ring-color: var(--color-destructive);
--_badge-bg: light-dark(
oklch(from var(--color-destructive) l c h / 0.1),
oklch(from var(--color-destructive) l c h / 0.2)
);
--_badge-text: light-dark(
oklch(from var(--color-destructive) min(l, 0.45) c h),
oklch(from var(--color-destructive) max(l, 0.82) c h)
);
}
:where([data-ui~="badge"][data-badge-variant="link"]) {
--_badge-bg: transparent;
--_badge-bg--hover: transparent;
--_badge-bg--active: transparent;
--_badge-text: var(--color-foreground);
--_badge-text--hover: oklch(from var(--color-primary) l c h / 0.6);
--_badge-text--active: oklch(from var(--color-primary) l c h / 0.5);
--_badge-border-style: none;
--_badge-px: 0;
--_badge-rounded: 0;
--_badge-shadow: 0 0 transparent;
}
/* ===========================================================================
BADGE SIZES — data-badge-size
=========================================================================== */
:where([data-ui~="badge"][data-badge-size="icon"]) {
inline-size: calc(var(--_h-len) + var(--_h-num) * var(--spacing));
padding: 0;
aspect-ratio: 1 / 1;
}
}Icon
<button type="button" data-ui="badge" data-badge-size="icon" aria-label="Icon badge default">
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none" />
<circle cx="128" cy="128" r="12" />
<circle cx="196" cy="128" r="12" />
<circle cx="60" cy="128" r="12" />
</svg>
</button>
<button
type="button"
data-ui="badge"
data-badge-size="icon"
data-badge-variant="primary"
aria-label="Icon badge primary"
>
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none" />
<circle cx="128" cy="128" r="12" />
<circle cx="196" cy="128" r="12" />
<circle cx="60" cy="128" r="12" />
</svg>
</button>
<button
type="button"
data-ui="badge"
data-badge-size="icon"
data-badge-variant="muted"
aria-label="Icon badge muted"
>
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none" />
<circle cx="128" cy="128" r="12" />
<circle cx="196" cy="128" r="12" />
<circle cx="60" cy="128" r="12" />
</svg>
</button>
<button
type="button"
data-ui="badge"
data-badge-size="icon"
data-badge-variant="ghost"
aria-label="Icon badge ghost"
>
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none" />
<circle cx="128" cy="128" r="12" />
<circle cx="196" cy="128" r="12" />
<circle cx="60" cy="128" r="12" />
</svg>
</button>/**
* badge.css: Badge ([data-ui~="badge"])
*
* @layer variables, components
* @requires _layers.css, _variables.css, _reset.css, _properties.css (--_*-resolved, --_focus-shadow), fields.css (--ui-field-* border/ring)
* @uses color-mix(), oklch(from …): variant hover/active tints
* @uses text-box: vertical trim (patchy browser support)
* @tokens --ui-badge-* (@layer variables; hooks are named after the utilities they back)
* @consumedby combobox.css (selection tags)
*
* Every utility-backed declaration reads one chain, highest first:
* var(--_<utility>-resolved, var(--_badge-<utility>, var(--ui-badge-<utility>)))
* States apply only to interactive badges (button, a[href]).
*/
@layer variables {
:root {
/* border + ring follow --ui-field-*; metrics stay independent (badges are smaller) */
--ui-badge-ring-color: var(--ui-field-ring-color);
--ui-badge-font-family: var(--font-family-body);
--ui-badge-font-size: var(--font-size-xs);
--ui-badge-font-weight: var(--font-weight-strong);
--ui-badge-leading: 1;
--ui-badge-bg: var(--color-background);
--ui-badge-bg--hover: var(--color-muted);
--ui-badge-bg--active: oklch(from var(--color-muted) l c h / calc(alpha - 0.02));
--ui-badge-text: var(--color-foreground);
--ui-badge-text--hover: var(--color-foreground);
--ui-badge-text--active: var(--color-foreground);
--ui-badge-border-width: var(--ui-field-border-width);
--ui-badge-border-style: var(--ui-field-border-style);
--ui-badge-border-color: var(--ui-field-border-color);
--ui-badge-border-color--hover: var(--ui-field-border-color);
--ui-badge-border-color--active: var(--ui-field-border-color);
/* dual-mode: a scale number or a length */
--ui-badge-gap: var(--spacing);
--ui-badge-px: calc(var(--spacing) * 1.25);
--ui-badge-h: calc(var(--spacing) * 5);
--ui-badge-rounded: var(--radius-sm);
--ui-badge-icon-size: calc(var(--spacing) * 3);
/* never `none`: the focus ring layers into this box-shadow list */
--ui-badge-shadow: 0 0 transparent;
}
}
/* Variant privates: non-inheriting so a variant never cascades into a nested badge. */
@property --_badge-ring-color {
syntax: "*";
inherits: false;
}
@property --_badge-font-family {
syntax: "*";
inherits: false;
}
@property --_badge-font-size {
syntax: "*";
inherits: false;
}
@property --_badge-font-weight {
syntax: "*";
inherits: false;
}
@property --_badge-leading {
syntax: "*";
inherits: false;
}
@property --_badge-bg {
syntax: "*";
inherits: false;
}
@property --_badge-bg--hover {
syntax: "*";
inherits: false;
}
@property --_badge-bg--active {
syntax: "*";
inherits: false;
}
@property --_badge-text {
syntax: "*";
inherits: false;
}
@property --_badge-text--hover {
syntax: "*";
inherits: false;
}
@property --_badge-text--active {
syntax: "*";
inherits: false;
}
@property --_badge-border-width {
syntax: "*";
inherits: false;
}
@property --_badge-border-style {
syntax: "*";
inherits: false;
}
@property --_badge-border-color {
syntax: "*";
inherits: false;
}
@property --_badge-border-color--hover {
syntax: "*";
inherits: false;
}
@property --_badge-border-color--active {
syntax: "*";
inherits: false;
}
@property --_badge-gap {
syntax: "*";
inherits: false;
}
@property --_badge-px {
syntax: "*";
inherits: false;
}
@property --_badge-h {
syntax: "*";
inherits: false;
}
@property --_badge-rounded {
syntax: "*";
inherits: false;
}
@property --_badge-icon-size {
syntax: "*";
inherits: false;
}
@property --_badge-shadow {
syntax: "*";
inherits: false;
}
@layer ui.components {
:where([data-ui~="badge"]) {
font-family: var(
--_font-family-resolved,
var(--_badge-font-family, var(--ui-badge-font-family))
);
font-size: var(--_font-size-resolved, var(--_badge-font-size, var(--ui-badge-font-size)));
font-weight: var(
--_font-weight-resolved,
var(--_badge-font-weight, var(--ui-badge-font-weight))
);
line-height: var(--_leading-resolved, var(--_badge-leading, var(--ui-badge-leading)));
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
--_gap-len: var(--_gap-resolved, var(--_badge-gap, var(--ui-badge-gap)));
--_gap-num: var(--_gap-resolved, var(--_badge-gap, var(--ui-badge-gap)));
gap: calc(var(--_gap-len) + var(--_gap-num) * var(--spacing));
--_h-len: var(--_h-resolved, var(--_badge-h, var(--ui-badge-h)));
--_h-num: var(--_h-resolved, var(--_badge-h, var(--ui-badge-h)));
block-size: calc(var(--_h-len) + var(--_h-num) * var(--spacing));
min-inline-size: max-content;
--_px-len: var(--_px-resolved, var(--_badge-px, var(--ui-badge-px)));
--_px-num: var(--_px-resolved, var(--_badge-px, var(--ui-badge-px)));
padding-inline: calc(var(--_px-len) + var(--_px-num) * var(--spacing));
border-width: var(
--_border-width-resolved,
var(--_badge-border-width, var(--ui-badge-border-width))
);
border-style: var(
--_border-style-resolved,
var(--_badge-border-style, var(--ui-badge-border-style))
);
border-color: var(
--_border-color-resolved,
var(--_badge-border-color, var(--ui-badge-border-color))
);
border-radius: var(--_rounded-resolved, var(--_badge-rounded, var(--ui-badge-rounded)));
color: var(--_text-resolved, var(--_badge-text, var(--ui-badge-text)));
background-color: var(--_bg-resolved, var(--_badge-bg, var(--ui-badge-bg)));
background-clip: padding-box;
white-space-collapse: collapse;
/* The ring is published to --_focus-shadow so the utilities layer's
box-shadow emission keeps it above a --shadow or --ring utility; the ring
widths grow from 0 on :focus-visible so the ring eases in. */
--_ring-offset-width: 0px;
--_ring-width: 0px;
--_focus-in: max(0, -1 * sign(var(--_focus-ring-w, 1px)));
--_ring-fill: color-mix(
in oklch,
var(--_ring-color-resolved, var(--_badge-ring-color, var(--ui-badge-ring-color)))
var(--ring-opacity),
transparent
);
--_focus-shadow:
0 0 0 calc(var(--_ring-offset-width) * (1 - var(--_focus-in))) var(--ring-offset-shadow-color),
0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * (1 - var(--_focus-in)))
var(--_ring-fill, var(--ring-shadow-color)),
inset 0 0 0 calc(var(--_ring-offset-width) * var(--_focus-in)) var(--ring-offset-shadow-color),
inset 0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * var(--_focus-in))
var(--_ring-fill, var(--ring-shadow-color));
box-shadow:
var(--_focus-shadow, 0 0 #0000),
var(--_shadow-resolved, var(--_badge-shadow, var(--ui-badge-shadow)));
cursor: default;
text-decoration: none;
/* transparent twin of the ring for forced-colors / high-contrast modes */
outline: abs(var(--_focus-ring-w, var(--outline-width))) var(--outline-style) transparent;
outline-offset: calc(
var(--outline-offset) - var(--_focus-in) *
(2 * var(--outline-offset) + abs(var(--_focus-ring-w, var(--outline-width))))
);
transition: var(--default-transition);
user-select: none;
text-box: trim-both ex alphabetic;
}
a[href] :where([data-ui~="badge"]):not(:disabled, [aria-disabled="true"]),
a[href]:where([data-ui~="badge"]):not(:disabled, [aria-disabled="true"]) {
cursor: pointer;
}
/* ===========================================================================
BADGE STATES — interactive badges only (button, a[href])
=========================================================================== */
@media (hover: hover) {
:where([data-ui~="badge"]):is(button, a[href]):hover:not(:disabled, [aria-disabled="true"]) {
color: var(--_text-resolved, var(--_badge-text--hover, var(--ui-badge-text--hover)));
background-color: var(--_bg-resolved, var(--_badge-bg--hover, var(--ui-badge-bg--hover)));
border-color: var(
--_border-color-resolved,
var(--_badge-border-color--hover, var(--ui-badge-border-color--hover))
);
}
}
:where([data-ui~="badge"]):is(button, a[href]):active:not(:disabled, [aria-disabled="true"]) {
color: var(--_text-resolved, var(--_badge-text--active, var(--ui-badge-text--active)));
background-color: var(--_bg-resolved, var(--_badge-bg--active, var(--ui-badge-bg--active)));
border-color: var(
--_border-color-resolved,
var(--_badge-border-color--active, var(--ui-badge-border-color--active))
);
}
:where([data-ui~="badge"]):is(button, a[href]):focus-visible:not(
:disabled,
[aria-disabled="true"]
) {
--_ring-offset-width: var(--ring-offset-width);
--_ring-width: abs(var(--_focus-ring-w, var(--ring-width)));
outline-color: transparent;
}
:where([data-ui~="badge"]):is(button, a[href])[aria-busy="true"] {
cursor: progress;
}
/* ===========================================================================
BADGE ICONS
=========================================================================== */
:where([data-ui~="badge"]) > svg {
inline-size: var(--ui-badge-icon-size);
block-size: var(--ui-badge-icon-size);
pointer-events: none;
justify-content: center;
align-items: center;
display: flex;
padding: 0;
}
/* ===========================================================================
BADGE VARIANTS — data-badge-variant writes the privates
=========================================================================== */
:where([data-ui~="badge"][data-badge-variant="primary"]) {
--_badge-bg: var(--color-primary);
--_badge-bg--hover: oklch(from var(--color-primary) l c h / 0.9);
--_badge-bg--active: oklch(from var(--color-primary) l c h / 0.8);
--_badge-text: var(--color-primary-foreground);
--_badge-text--hover: var(--color-primary-foreground);
--_badge-text--active: var(--color-primary-foreground);
--_badge-border-color: transparent;
--_badge-border-color--hover: transparent;
--_badge-border-color--active: transparent;
}
:where([data-ui~="badge"][data-badge-variant="secondary"]) {
--_badge-bg: var(--color-secondary);
--_badge-bg--hover: oklch(from var(--color-secondary) l c h / 0.9);
--_badge-bg--active: oklch(from var(--color-secondary) l c h / 0.8);
--_badge-text: var(--color-secondary-foreground);
--_badge-text--hover: var(--color-secondary-foreground);
--_badge-text--active: var(--color-secondary-foreground);
--_badge-border-color: transparent;
--_badge-border-color--hover: transparent;
--_badge-border-color--active: transparent;
}
:where([data-ui~="badge"][data-badge-variant="tertiary"]) {
--_badge-bg: var(--color-tertiary);
--_badge-bg--hover: oklch(from var(--color-tertiary) l c h / 0.9);
--_badge-bg--active: oklch(from var(--color-tertiary) l c h / 0.8);
--_badge-text: var(--color-tertiary-foreground);
--_badge-text--hover: var(--color-tertiary-foreground);
--_badge-text--active: var(--color-tertiary-foreground);
--_badge-border-color: transparent;
--_badge-border-color--hover: transparent;
--_badge-border-color--active: transparent;
}
:where([data-ui~="badge"][data-badge-variant="muted"]) {
--_badge-bg: var(--color-muted);
--_badge-bg--hover: oklch(from var(--color-muted) l c h / calc(alpha - 0.02));
--_badge-bg--active: var(--color-muted);
--_badge-text: var(--color-foreground);
--_badge-text--hover: var(--color-foreground);
--_badge-text--active: var(--color-foreground);
--_badge-border-color: transparent;
--_badge-border-color--hover: transparent;
--_badge-border-color--active: transparent;
}
:where([data-ui~="badge"][data-badge-variant="ghost"]) {
--_badge-bg: transparent;
--_badge-bg--hover: var(--color-muted);
--_badge-bg--active: oklch(from var(--color-muted) l c h / calc(alpha - 0.02));
--_badge-text: var(--color-foreground);
--_badge-text--hover: var(--color-foreground);
--_badge-text--active: var(--color-foreground);
--_badge-border-color: transparent;
--_badge-border-color--hover: transparent;
--_badge-border-color--active: transparent;
}
/* Status variants (info, success, warning, destructive) share one soft
design: the status color at 10% alpha (20% in dark) as the surface, and
the status hue as the text with its lightness clamped (at most 0.45 in
light, at least 0.82 in dark) so it holds 4.5:1 on the tint and on the
hover and active steps; status-contrast.browser.test.ts checks it. No border. One rule derives the states from the
surface; each variant names only its colors. */
:where(
[data-ui~="badge"]:is(
[data-badge-variant="info"],
[data-badge-variant="success"],
[data-badge-variant="warning"],
[data-badge-variant="destructive"]
)
) {
--_badge-bg--hover: oklch(from var(--_badge-bg) l c h / calc(alpha + 0.1));
--_badge-bg--active: oklch(from var(--_badge-bg) l c h / calc(alpha + 0.15));
--_badge-text--hover: var(--_badge-text);
--_badge-text--active: var(--_badge-text);
--_badge-border-color: transparent;
--_badge-border-color--hover: transparent;
--_badge-border-color--active: transparent;
--_badge-shadow: 0 0 transparent;
}
:where([data-ui~="badge"][data-badge-variant="info"]) {
--_badge-ring-color: var(--color-info);
--_badge-bg: light-dark(
oklch(from var(--color-info) l c h / 0.1),
oklch(from var(--color-info) l c h / 0.2)
);
--_badge-text: light-dark(
oklch(from var(--color-info) min(l, 0.45) c h),
oklch(from var(--color-info) max(l, 0.82) c h)
);
}
:where([data-ui~="badge"][data-badge-variant="success"]) {
--_badge-ring-color: var(--color-success);
--_badge-bg: light-dark(
oklch(from var(--color-success) l c h / 0.1),
oklch(from var(--color-success) l c h / 0.2)
);
--_badge-text: light-dark(
oklch(from var(--color-success) min(l, 0.45) c h),
oklch(from var(--color-success) max(l, 0.82) c h)
);
}
:where([data-ui~="badge"][data-badge-variant="warning"]) {
--_badge-ring-color: var(--color-warning);
--_badge-bg: light-dark(
oklch(from var(--color-warning) l c h / 0.1),
oklch(from var(--color-warning) l c h / 0.2)
);
--_badge-text: light-dark(
oklch(from var(--color-warning) min(l, 0.45) c h),
oklch(from var(--color-warning) max(l, 0.82) c h)
);
}
:where([data-ui~="badge"][data-badge-variant="destructive"]) {
--_badge-ring-color: var(--color-destructive);
--_badge-bg: light-dark(
oklch(from var(--color-destructive) l c h / 0.1),
oklch(from var(--color-destructive) l c h / 0.2)
);
--_badge-text: light-dark(
oklch(from var(--color-destructive) min(l, 0.45) c h),
oklch(from var(--color-destructive) max(l, 0.82) c h)
);
}
:where([data-ui~="badge"][data-badge-variant="link"]) {
--_badge-bg: transparent;
--_badge-bg--hover: transparent;
--_badge-bg--active: transparent;
--_badge-text: var(--color-foreground);
--_badge-text--hover: oklch(from var(--color-primary) l c h / 0.6);
--_badge-text--active: oklch(from var(--color-primary) l c h / 0.5);
--_badge-border-style: none;
--_badge-px: 0;
--_badge-rounded: 0;
--_badge-shadow: 0 0 transparent;
}
/* ===========================================================================
BADGE SIZES — data-badge-size
=========================================================================== */
:where([data-ui~="badge"][data-badge-size="icon"]) {
inline-size: calc(var(--_h-len) + var(--_h-num) * var(--spacing));
padding: 0;
aspect-ratio: 1 / 1;
}
}Attributes
| Attribute | Kind | Values | Notes |
|---|---|---|---|
data-ui="badge" | Identity | free value | Identity token in data-ui. |
data-badge-size | Preset | icon | Preset: a named bundle of values. |
data-badge-variant | Preset | destructive ghost info link muted primary secondary success tertiary warning | Preset: a named bundle of values. |
Requires: fields.
Hooks
Set a hook on :root, on a subtree, or inline on one element. A state hook (--…--hover) themes that state only; the primitive keeps its other states.
| Hook | Default | State |
|---|---|---|
--ui-badge-bg | var(--color-background) | — |
--ui-badge-bg--active | oklch(from var(--color-muted) l c h / calc(alpha - 0.02)) | active |
--ui-badge-bg--hover | var(--color-muted) | hover |
--ui-badge-border-color | var(--ui-field-border-color) | — |
--ui-badge-border-color--active | var(--ui-field-border-color) | active |
--ui-badge-border-color--hover | var(--ui-field-border-color) | hover |
--ui-badge-border-style | var(--ui-field-border-style) | — |
--ui-badge-border-width | var(--ui-field-border-width) | — |
--ui-badge-font-family | var(--font-family-body) | — |
--ui-badge-font-size | var(--font-size-xs) | — |
--ui-badge-font-weight | var(--font-weight-strong) | — |
--ui-badge-gap | var(--spacing) | — |
--ui-badge-h | calc(var(--spacing) * 5) | — |
--ui-badge-icon-size | calc(var(--spacing) * 3) | — |
--ui-badge-leading | 1 | — |
--ui-badge-px | calc(var(--spacing) * 1.25) | — |
--ui-badge-ring-color | var(--ui-field-ring-color) | — |
--ui-badge-rounded | var(--radius-sm) | — |
--ui-badge-shadow | 0 0 transparent | — |
--ui-badge-text | var(--color-foreground) | — |
--ui-badge-text--active | var(--color-foreground) | active |
--ui-badge-text--hover | var(--color-foreground) | hover |