component — display
Badge
The quebi tag: a lowercase pill on the raised ground. Nine intents — only success, warning and danger take a colour — plus an optional state dot for live labels.
- badge
- label
- status
- pill
- tag
02 examples
Intents
Nine intents, one hue-free tag. Only the three states take a colour; ai is the slate fill, outline a hairline.
neutralfeaturedbest valuelivereviewoverdueinfoai matcharchived
With dot
Prepend a dot for live-state indicators — it inherits the badge text colour.
live on kioskdraftoverdue
source
badge.tsxCopy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.
import type { VariantProps } from "tailwind-variants"
import { cn, tv } from "@/lib/utils"
/**
* The design's Tag, per intent: a hairline-edged mark in the subtle ink.
*
* There is no hue here. `brand`, `accent` and `info` are the plain tag — they
* survive as names so call sites keep compiling, but emphasis in this system
* is size, weight or ink, not colour; the signal is state, never a tag. The
* three state intents are the only tints: the state token as text over a 10%
* wash of itself. `ai` is the one fill, the slate action, and `outline` is the
* plain tag under its own name.
*
* Exported because `iconTileIntents` in `icon-tile.tsx` shares the key set and
* the three state tints, pinned by `tests/components/icon-tile.test.tsx`.
*/
export const badgeIntents = {
neutral: "border-quebi-hairline bg-transparent text-quebi-fg-subtle",
brand: "border-quebi-hairline bg-transparent text-quebi-fg-subtle",
accent: "border-quebi-hairline bg-transparent text-quebi-fg-subtle",
success: "border-transparent bg-quebi-success/10 text-quebi-success",
warning: "border-transparent bg-quebi-warn/10 text-quebi-warn",
danger: "border-transparent bg-quebi-danger/10 text-quebi-danger",
info: "border-quebi-hairline bg-transparent text-quebi-fg-subtle",
ai: "border-transparent bg-quebi-action text-quebi-on-action",
outline: "border-quebi-hairline bg-transparent text-quebi-fg-subtle",
}
/**
* Badge — quebi design system
*
* The Tag: mono uppercase at the label size (`text-quebi-label`) in a 1px
* box at the mark radius. The border is always present (transparent on the
* filled intents) so every badge in a row is the same size.
*
* Intents: neutral (default), brand, accent, success, warning, danger, info,
* ai, outline. A badge with no text, holding only an icon, is an `IconTile`.
*/
export const badgeStyles = tv({
base: [
"inline-flex items-center gap-1.5",
"font-mono text-quebi-label uppercase whitespace-nowrap",
"rounded-(--q-radius-mark) border px-1.5 py-0.5",
],
variants: {
intent: badgeIntents,
},
defaultVariants: {
intent: "neutral",
},
})
export interface BadgeProps
extends React.ComponentProps<"span">,
VariantProps<typeof badgeStyles> {}
export function Badge({ intent, className, children, ...props }: BadgeProps) {
return (
<span {...props} className={cn(badgeStyles({ intent }), className)}>
{children}
</span>
)
}
/**
* Prepends a 6px dot — use for live-state indicators ("live", "draft",
* "overdue"). The dot inherits the badge's text colour.
*/
export function BadgeDot() {
return <span aria-hidden="true" className="size-1.5 rounded-full bg-current" />
}