Components
Badge
Status and category chips that use the standard data-variant system.
Badges use the .ui-badge class. Set colors with data-variant (default when omitted). Set data-size="icon" for square icon badges.
Badges can be static <span> elements or interactive <button> / <a> elements with hover/active states and disabled styling.
Variants
Loading components…
<div class="flex items-center gap-sm"> <button type="button" class="ui-badge">Category</button> <button type="button" class="ui-badge" data-variant="primary">New</button> <button type="button" class="ui-badge" data-variant="muted">Draft</button> <button type="button" class="ui-badge" data-variant="ghost">Inline</button> <button type="button" class="ui-badge" data-variant="link">Posts</button></div>/** * badge.css: Badge (.ui-badge) * * @layer variables, components * @requires layers.css, _variables.css, _reset.css * @uses color-mix(), oklch(from …): variant hover/active tints * @uses text-box: vertical trim (patchy browser support) * @tokens --ui-badge-* (@layer variables) * @consumedby combobox.css (selection tags) */@layer variables { :root { --ui-badge-ring-color: var(--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-line-height: 1; --ui-badge-background: var(--background); --ui-badge-background--hover: var(--muted); --ui-badge-background--active: oklch(from var(--muted) l c h / calc(alpha - 0.02)); --ui-badge-foreground: var(--foreground); --ui-badge-foreground--hover: var(--foreground); --ui-badge-foreground--active: var(--foreground); --ui-badge-border: 1px solid var(--border); --ui-badge-border--hover: 1px solid var(--border); --ui-badge-border--active: 1px solid var(--border); --ui-badge-gap: var(--step-1); --ui-badge-padding: var(--step-1_5); --ui-badge-height: var(--step-5_5); --ui-badge-radius: var(--radius-sm); --ui-badge-icon-size: var(--step-3); /* composes into the badge's box-shadow list; use a transparent shadow, never `none`, so the focus ring layers stay valid */ --ui-badge-shadow: 0 0 transparent; }}@layer zazz.components { /* =========================================================================== BADGE =========================================================================== */ .ui-badge { font-family: var(--ui-badge-font-family); font-size: var(--ui-badge-font-size); font-weight: var(--ui-badge-font-weight); line-height: var(--ui-badge-line-height); display: flex; align-items: center; justify-content: center; gap: var(--ui-badge-gap); flex-shrink: 0; block-size: var(--ui-badge-height); min-width: max-content; padding-inline: var(--ui-badge-padding); border: var(--ui-badge-border); border-radius: var(--ui-badge-radius); color: var(--ui-badge-foreground); background-color: var(--ui-badge-background); /* ring renders as box-shadow so it composes with --ui-badge-shadow; the ring widths animate from 0 so the ring grows in smoothly on focus */ --_ring-offset-width: 0px; --_ring-width: 0px; --_ring: color-mix(in oklch, var(--ui-badge-ring-color) var(--ring-opacity), transparent); box-shadow: 0 0 0 var(--_ring-offset-width) var(--ring-offset-color), 0 0 0 calc(var(--_ring-offset-width) + var(--_ring-width)) var(--_ring, var(--ring)), var(--ui-badge-shadow); cursor: default; text-decoration: none; /* transparent twin of the ring for forced-colors / high-contrast modes */ outline: var(--outline-width) var(--outline-style) transparent; outline-offset: var(--outline-offset); transition: var(--default-transition); user-select: none; text-box: trim-both ex alphabetic; } a[href] .ui-badge:not(:disabled, [aria-disabled="true"]), a[href].ui-badge:not(:disabled, [aria-disabled="true"]) { cursor: pointer; } /* =========================================================================== BADGE STATES =========================================================================== */ button.ui-badge:hover:not(:disabled, [aria-disabled="true"]), a[href].ui-badge:hover:not(:disabled, [aria-disabled="true"]) { color: var(--ui-badge-foreground--hover); background-color: var(--ui-badge-background--hover); border: var(--ui-badge-border--hover); } button.ui-badge:active:not(:disabled, [aria-disabled="true"]), a[href].ui-badge:active:not(:disabled, [aria-disabled="true"]) { color: var(--ui-badge-foreground--active); background-color: var(--ui-badge-background--active); border: var(--ui-badge-border--active); } button.ui-badge:focus-visible:hover:not(:disabled, [aria-disabled="true"]), a[href].ui-badge:focus-visible:hover:not(:disabled, [aria-disabled="true"]) { --_ring-offset-width: var(--ring-offset-width); --_ring-width: var(--ring-width); outline-color: transparent; } :is(button.ui-badge:disabled, a[href].ui-badge:disabled), :is(button.ui-badge[aria-disabled="true"], a[href].ui-badge[aria-disabled="true"]) { --ui-badge-foreground: var(--muted-foreground); --ui-badge-background: var(--muted); --ui-badge-shadow: 0 0 transparent; cursor: default; opacity: 0.5; outline: none; } button.ui-badge[aria-busy="true"], a[href].ui-badge[aria-busy="true"] { cursor: progress; } /* =========================================================================== BADGE ICONS =========================================================================== */ .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 =========================================================================== */ .ui-badge[data-variant="primary"] { --ui-badge-background: var(--primary); --ui-badge-background--hover: oklch(from var(--primary) l c h / 0.9); --ui-badge-background--active: oklch(from var(--primary) l c h / 0.8); --ui-badge-foreground: var(--primary-foreground); --ui-badge-foreground--hover: var(--primary-foreground); --ui-badge-foreground--active: var(--primary-foreground); --ui-badge-border: 1px solid transparent; --ui-badge-border--hover: 1px solid transparent; --ui-badge-border--active: 1px solid transparent; } .ui-badge[data-variant="muted"] { --ui-badge-background: var(--muted); --ui-badge-background--hover: oklch(from var(--muted) l c h / calc(alpha - 0.02)); --ui-badge-background--active: var(--muted); --ui-badge-foreground: var(--foreground); --ui-badge-foreground--hover: var(--foreground); --ui-badge-foreground--active: var(--foreground); --ui-badge-border: 1px solid transparent; --ui-badge-border--hover: 1px solid transparent; --ui-badge-border--active: 1px solid transparent; } .ui-badge[data-variant="ghost"] { --ui-badge-background: transparent; --ui-badge-background--hover: var(--muted); --ui-badge-background--active: oklch(from var(--muted) l c h / calc(alpha - 0.02)); --ui-badge-foreground: var(--foreground); --ui-badge-foreground--hover: var(--foreground); --ui-badge-foreground--active: var(--foreground); --ui-badge-border: 1px solid transparent; --ui-badge-border--hover: 1px solid transparent; --ui-badge-border--active: 1px solid transparent; } .ui-badge[data-variant="link"] { --ui-badge-background: transparent; --ui-badge-background--hover: transparent; --ui-badge-background--active: transparent; --ui-badge-foreground: var(--foreground); --ui-badge-foreground--hover: var(--primary); --ui-badge-foreground--active: oklch(from var(--primary) l c h / 0.8); --ui-badge-border: none; --ui-badge-border--hover: none; --ui-badge-border--active: none; --ui-badge-height: fit-content; --ui-badge-padding: 0; --ui-badge-radius: 0; --ui-badge-shadow: 0 0 transparent; text-decoration-line: var(--decoration-line); text-decoration-style: var(--decoration-style); text-decoration-color: var(--decoration-color); text-underline-offset: var(--decoration-offset); text-decoration-thickness: var(--decoration-thickness); } button.ui-badge[data-variant="link"]:hover:not(:disabled, [aria-disabled="true"]), a[href].ui-badge[data-variant="link"]:hover:not(:disabled, [aria-disabled="true"]) { text-decoration-color: var(--decoration-color--hover); text-underline-offset: var(--decoration-offset--hover); } button.ui-badge[data-variant="link"]:active:not(:disabled, [aria-disabled="true"]), a[href].ui-badge[data-variant="link"]:active:not(:disabled, [aria-disabled="true"]) { text-decoration-color: var(--decoration-color--active); text-underline-offset: var(--decoration-offset--active); } /* =========================================================================== BADGE SIZES =========================================================================== */ .ui-badge[data-size="icon"] { block-size: var(--ui-badge-height); inline-size: var(--ui-badge-height); padding: 0; aspect-ratio: 1 / 1; }}Icon badge
Include <span class="sr-only"> for accessible names on icon-only badges:
Loading components…
<button type="button" class="ui-badge" data-size="icon"> <span class="sr-only">Icon badge default</span> <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" class="ui-badge" data-size="icon" data-variant="primary"> <span class="sr-only">Icon badge primary</span> <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" class="ui-badge" data-size="icon" data-variant="muted"> <span class="sr-only">Icon badge muted</span> <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" class="ui-badge" data-size="icon" data-variant="ghost"> <span class="sr-only">Icon badge ghost</span> <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 (.ui-badge) * * @layer variables, components * @requires layers.css, _variables.css, _reset.css * @uses color-mix(), oklch(from …): variant hover/active tints * @uses text-box: vertical trim (patchy browser support) * @tokens --ui-badge-* (@layer variables) * @consumedby combobox.css (selection tags) */@layer variables { :root { --ui-badge-ring-color: var(--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-line-height: 1; --ui-badge-background: var(--background); --ui-badge-background--hover: var(--muted); --ui-badge-background--active: oklch(from var(--muted) l c h / calc(alpha - 0.02)); --ui-badge-foreground: var(--foreground); --ui-badge-foreground--hover: var(--foreground); --ui-badge-foreground--active: var(--foreground); --ui-badge-border: 1px solid var(--border); --ui-badge-border--hover: 1px solid var(--border); --ui-badge-border--active: 1px solid var(--border); --ui-badge-gap: var(--step-1); --ui-badge-padding: var(--step-1_5); --ui-badge-height: var(--step-5_5); --ui-badge-radius: var(--radius-sm); --ui-badge-icon-size: var(--step-3); /* composes into the badge's box-shadow list; use a transparent shadow, never `none`, so the focus ring layers stay valid */ --ui-badge-shadow: 0 0 transparent; }}@layer zazz.components { /* =========================================================================== BADGE =========================================================================== */ .ui-badge { font-family: var(--ui-badge-font-family); font-size: var(--ui-badge-font-size); font-weight: var(--ui-badge-font-weight); line-height: var(--ui-badge-line-height); display: flex; align-items: center; justify-content: center; gap: var(--ui-badge-gap); flex-shrink: 0; block-size: var(--ui-badge-height); min-width: max-content; padding-inline: var(--ui-badge-padding); border: var(--ui-badge-border); border-radius: var(--ui-badge-radius); color: var(--ui-badge-foreground); background-color: var(--ui-badge-background); /* ring renders as box-shadow so it composes with --ui-badge-shadow; the ring widths animate from 0 so the ring grows in smoothly on focus */ --_ring-offset-width: 0px; --_ring-width: 0px; --_ring: color-mix(in oklch, var(--ui-badge-ring-color) var(--ring-opacity), transparent); box-shadow: 0 0 0 var(--_ring-offset-width) var(--ring-offset-color), 0 0 0 calc(var(--_ring-offset-width) + var(--_ring-width)) var(--_ring, var(--ring)), var(--ui-badge-shadow); cursor: default; text-decoration: none; /* transparent twin of the ring for forced-colors / high-contrast modes */ outline: var(--outline-width) var(--outline-style) transparent; outline-offset: var(--outline-offset); transition: var(--default-transition); user-select: none; text-box: trim-both ex alphabetic; } a[href] .ui-badge:not(:disabled, [aria-disabled="true"]), a[href].ui-badge:not(:disabled, [aria-disabled="true"]) { cursor: pointer; } /* =========================================================================== BADGE STATES =========================================================================== */ button.ui-badge:hover:not(:disabled, [aria-disabled="true"]), a[href].ui-badge:hover:not(:disabled, [aria-disabled="true"]) { color: var(--ui-badge-foreground--hover); background-color: var(--ui-badge-background--hover); border: var(--ui-badge-border--hover); } button.ui-badge:active:not(:disabled, [aria-disabled="true"]), a[href].ui-badge:active:not(:disabled, [aria-disabled="true"]) { color: var(--ui-badge-foreground--active); background-color: var(--ui-badge-background--active); border: var(--ui-badge-border--active); } button.ui-badge:focus-visible:hover:not(:disabled, [aria-disabled="true"]), a[href].ui-badge:focus-visible:hover:not(:disabled, [aria-disabled="true"]) { --_ring-offset-width: var(--ring-offset-width); --_ring-width: var(--ring-width); outline-color: transparent; } :is(button.ui-badge:disabled, a[href].ui-badge:disabled), :is(button.ui-badge[aria-disabled="true"], a[href].ui-badge[aria-disabled="true"]) { --ui-badge-foreground: var(--muted-foreground); --ui-badge-background: var(--muted); --ui-badge-shadow: 0 0 transparent; cursor: default; opacity: 0.5; outline: none; } button.ui-badge[aria-busy="true"], a[href].ui-badge[aria-busy="true"] { cursor: progress; } /* =========================================================================== BADGE ICONS =========================================================================== */ .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 =========================================================================== */ .ui-badge[data-variant="primary"] { --ui-badge-background: var(--primary); --ui-badge-background--hover: oklch(from var(--primary) l c h / 0.9); --ui-badge-background--active: oklch(from var(--primary) l c h / 0.8); --ui-badge-foreground: var(--primary-foreground); --ui-badge-foreground--hover: var(--primary-foreground); --ui-badge-foreground--active: var(--primary-foreground); --ui-badge-border: 1px solid transparent; --ui-badge-border--hover: 1px solid transparent; --ui-badge-border--active: 1px solid transparent; } .ui-badge[data-variant="muted"] { --ui-badge-background: var(--muted); --ui-badge-background--hover: oklch(from var(--muted) l c h / calc(alpha - 0.02)); --ui-badge-background--active: var(--muted); --ui-badge-foreground: var(--foreground); --ui-badge-foreground--hover: var(--foreground); --ui-badge-foreground--active: var(--foreground); --ui-badge-border: 1px solid transparent; --ui-badge-border--hover: 1px solid transparent; --ui-badge-border--active: 1px solid transparent; } .ui-badge[data-variant="ghost"] { --ui-badge-background: transparent; --ui-badge-background--hover: var(--muted); --ui-badge-background--active: oklch(from var(--muted) l c h / calc(alpha - 0.02)); --ui-badge-foreground: var(--foreground); --ui-badge-foreground--hover: var(--foreground); --ui-badge-foreground--active: var(--foreground); --ui-badge-border: 1px solid transparent; --ui-badge-border--hover: 1px solid transparent; --ui-badge-border--active: 1px solid transparent; } .ui-badge[data-variant="link"] { --ui-badge-background: transparent; --ui-badge-background--hover: transparent; --ui-badge-background--active: transparent; --ui-badge-foreground: var(--foreground); --ui-badge-foreground--hover: var(--primary); --ui-badge-foreground--active: oklch(from var(--primary) l c h / 0.8); --ui-badge-border: none; --ui-badge-border--hover: none; --ui-badge-border--active: none; --ui-badge-height: fit-content; --ui-badge-padding: 0; --ui-badge-radius: 0; --ui-badge-shadow: 0 0 transparent; text-decoration-line: var(--decoration-line); text-decoration-style: var(--decoration-style); text-decoration-color: var(--decoration-color); text-underline-offset: var(--decoration-offset); text-decoration-thickness: var(--decoration-thickness); } button.ui-badge[data-variant="link"]:hover:not(:disabled, [aria-disabled="true"]), a[href].ui-badge[data-variant="link"]:hover:not(:disabled, [aria-disabled="true"]) { text-decoration-color: var(--decoration-color--hover); text-underline-offset: var(--decoration-offset--hover); } button.ui-badge[data-variant="link"]:active:not(:disabled, [aria-disabled="true"]), a[href].ui-badge[data-variant="link"]:active:not(:disabled, [aria-disabled="true"]) { text-decoration-color: var(--decoration-color--active); text-underline-offset: var(--decoration-offset--active); } /* =========================================================================== BADGE SIZES =========================================================================== */ .ui-badge[data-size="icon"] { block-size: var(--ui-badge-height); inline-size: var(--ui-badge-height); padding: 0; aspect-ratio: 1 / 1; }}API
| Attribute | Values |
|---|---|
data-variant | primary, muted, ghost, link (omit = default) |
data-size | icon |
To apply status colors, override component tokens:
<span
class="ui-badge"
style="--ui-badge-background: var(--success); --ui-badge-foreground: var(--success-foreground)"
>
Active
</span>