Badge
A little context, right where it matters. Use Badge for status, counts, versions, and short labels that help readers understand the surrounding content.
BadgeBadge renders an inline span with two appearance props and a default slot. Its content describes something; use Btn or BtnLink when the reader should take an action.
Playground
Edit the label and explore every tone and variant. The default surface treatment works well for supporting metadata. A stronger treatment can make a short, important status easier to find.
<RegorApp id="badge-playground" src="./badge-playground.ts"/> import {
type ComponentVariant,
defineBadgeComponents,
defineButtonComponents,
defineFlexComponents,
defineFormInputField,
defineFormSelectField,
defineGridComponents,
defineIconComponents,
type FormSelectOption,
} from '@purestack/ts-components'
import { SEMANTIC_TONES, type SemanticTone } from '@purestack/ts-style'
import { lucide_chevron_down } from '@purestack/ts-svg-icons'
import { batch, createApp, defineComponent, html, type Ref, ref } from 'regor'
export interface BadgePlayground {
badgeLabel: Ref<string>
tone: Ref<SemanticTone>
variant: Ref<ComponentVariant>
tones: FormSelectOption[]
variants: FormSelectOption[]
reset: () => void
}
const badgePlaygroundTemplate = html`<Flex direction="column">
<Flex align="center" justify="center" class="p-4">
<Badge :tone="tone" :variant="variant">{{ badgeLabel }}</Badge>
</Flex>
<Grid columns="1" columnsSm="2" columnsLg="3">
<FormInputField id="badge-label" label="Label" :model="badgeLabel"/>
<FormSelectField id="badge-tone" label="Tone" :model="tone" :options="tones"/>
<FormSelectField id="badge-variant" label="Variant" :model="variant" :options="variants"/>
</Grid>
<Btn variant="link" @click="reset">Reset playground</Btn>
</Flex>`
function createBadgePlayground(): BadgePlayground {
const badgeLabel = ref('Stable')
const tone = ref<SemanticTone>('success')
const variant = ref<ComponentVariant>('surface')
return {
badgeLabel,
tone,
variant,
tones: SEMANTIC_TONES.map((value) => ({ label: value, value })),
variants: [
'solid',
'surface',
'surfaceAlt',
'spotlight',
'glass',
'flat',
'flatAlt',
'flatSolid',
'outlineFill',
'outline',
'subtle',
'subtleBtn',
'link',
'sheen',
'underline',
'rail',
'bracket',
'none',
].map((value) => ({ label: value, value })),
reset: () =>
batch(() => {
badgeLabel('Stable')
tone('success')
variant('surface')
}),
}
}
const component = defineComponent<BadgePlayground>(badgePlaygroundTemplate, {
context: createBadgePlayground,
})
createApp(
{
components: {
BadgePlayground: component,
...defineBadgeComponents(),
...defineButtonComponents(),
...defineFlexComponents(),
...defineGridComponents(),
...defineFormInputField(),
...defineFormSelectField(),
...defineIconComponents((name) => {
if (name !== 'lucide:chevron-down')
throw new Error(`Icon is not registered: ${name}`)
return lucide_chevron_down
}),
},
},
{ selector: 'app#badge-playground', template: html`<BadgePlayground/>` },
) Tones
Use tone to reinforce the meaning already expressed by the label. “Passing”, “Review needed”, and “Failed” remain understandable without their colors.
<Flex wrap="true" align="center">
<Badge tone="neutral">neutral</Badge>
<Badge tone="accent">accent</Badge>
<Badge tone="feature">feature</Badge>
<Badge tone="secondary">secondary</Badge>
<Badge tone="custom">custom</Badge>
<Badge tone="ghost">ghost</Badge>
<Badge tone="info">info</Badge>
<Badge tone="success">success</Badge>
<Badge tone="warning">warning</Badge>
<Badge tone="danger">danger</Badge>
</Flex> Variants
Badge accepts every shared variant and defaults to surface. Its variants use stateless styling: they do not add the hover and active behavior used by buttons. Interaction-oriented treatments may therefore look similar on a badge.
<Flex wrap="true" align="center">
<Badge tone="accent" variant="solid">solid</Badge>
<Badge tone="accent" variant="surface">surface</Badge>
<Badge tone="accent" variant="surfaceAlt">surfaceAlt</Badge>
<Badge tone="accent" variant="spotlight">spotlight</Badge>
<Badge tone="accent" variant="glass">glass</Badge>
<Badge tone="accent" variant="flat">flat</Badge>
<Badge tone="accent" variant="flatAlt">flatAlt</Badge>
<Badge tone="accent" variant="flatSolid">flatSolid</Badge>
<Badge tone="accent" variant="outlineFill">outlineFill</Badge>
<Badge tone="accent" variant="outline">outline</Badge>
<Badge tone="accent" variant="subtle">subtle</Badge>
<Badge tone="accent" variant="subtleBtn">subtleBtn</Badge>
<Badge tone="accent" variant="link">link</Badge>
<Badge tone="accent" variant="sheen">sheen</Badge>
<Badge tone="accent" variant="underline">underline</Badge>
<Badge tone="accent" variant="rail">rail</Badge>
<Badge tone="accent" variant="bracket">bracket</Badge>
<Badge tone="accent" variant="none">none</Badge>
</Flex> Inline composition
The default slot accepts text and inline components. Put a badge beside a heading, give a count an explicit accessible name, or add a decorative icon next to a meaningful status label. Badge has no icon or size prop.
Open reviews 3
All checks passed<Flex direction="column" align="start">
<Flex wrap="true" align="center">
<strong>Public API</strong>
<Badge tone="success">Stable</Badge>
<Badge variant="outline">v1.0</Badge>
</Flex>
<p class="m-0">Open reviews <Badge tone="info" aria-label="3 open reviews">3</Badge></p>
<Badge tone="success" variant="outline">
<Icon name="tabler:check"/>
All checks passed
</Badge>
</Flex> Live release status
Complete the three checks to enable publishing. The badge counts completed checks, switches to a success tone when ready, and becomes “Published” after you publish the preview. Reset starts a new checklist.
This example runs entirely in your browser. It models a release workflow without sending data. A named checklist, disabled action, and live status region make the changing state understandable beyond color.
<RegorApp id="release-checklist" src="./release-checklist.ts"/> import {
defineBadgeComponents,
defineBtnGroupComponents,
defineButtonComponents,
defineFlexComponents,
defineFormComponents,
} from '@purestack/ts-components'
import type { SemanticTone } from '@purestack/ts-style'
import {
batch,
type ComputedRef,
computed,
createApp,
defineComponent,
html,
type Ref,
ref,
} from 'regor'
export interface ReleaseChecklist {
documentation: Ref<boolean>
tests: Ref<boolean>
review: Ref<boolean>
published: Ref<boolean>
completed: ComputedRef<number>
status: ComputedRef<string>
tone: ComputedRef<SemanticTone>
message: ComputedRef<string>
publishDisabled: ComputedRef<boolean>
publish: () => void
reset: () => void
}
const releaseChecklistTemplate = html`<Flex direction="column" align="start">
<fieldset class="m-0 p-3 w-full">
<legend>Release checklist</legend>
<Flex direction="column" align="start">
<FormCheck id="release-documentation" label="Documentation is up to date" :checked="documentation" :disabled="published"/>
<FormCheck id="release-tests" label="Tests are passing" :checked="tests" :disabled="published"/>
<FormCheck id="release-review" label="Review is complete" :checked="review" :disabled="published"/>
</Flex>
</fieldset>
<Flex align="center" wrap="true" role="status">
<Badge :tone="tone">{{ status }}</Badge>
<span>{{ message }}</span>
</Flex>
<BtnGroup :wrap="true" role="group" aria-label="Release actions">
<Btn tone="accent" :disabled="publishDisabled" @click="publish">Publish preview</Btn>
<Btn variant="surface" @click="reset">Reset checklist</Btn>
</BtnGroup>
</Flex>`
function createReleaseChecklist(): ReleaseChecklist {
const documentation = ref(false)
const tests = ref(false)
const review = ref(false)
const published = ref(false)
const completed = computed(
() => Number(documentation()) + Number(tests()) + Number(review()),
)
const status = computed(() =>
published() ? 'Published' : `${completed()}/3 ready`,
)
const tone = computed<SemanticTone>(() =>
published() || completed() === 3 ? 'success' : 'warning',
)
const message = computed<string>(() =>
published()
? 'Preview release published.'
: completed() === 3
? 'All checks passed. Ready to publish.'
: 'Complete every check to publish the preview.',
)
const publishDisabled = computed(() => completed() !== 3 || published())
return {
documentation,
tests,
review,
published,
completed,
status,
tone,
message,
publishDisabled,
publish: () => {
if (!publishDisabled()) published(true)
},
reset: () =>
batch(() => {
documentation(false)
tests(false)
review(false)
published(false)
}),
}
}
const component = defineComponent<ReleaseChecklist>(releaseChecklistTemplate, {
context: createReleaseChecklist,
})
createApp(
{
components: {
ReleaseChecklist: component,
...defineBadgeComponents(),
...defineBtnGroupComponents(),
...defineButtonComponents(),
...defineFlexComponents(),
...defineFormComponents(),
},
},
{ selector: 'app#release-checklist', template: html`<ReleaseChecklist/>` },
) Accessibility
- Include a readable label that carries the meaning. Tone alone is not a status message.
- Badge is a non-interactive
span. It has no automatic role, tab stop, or keyboard action. - For a changing status that needs announcing, place the badge and its explanation inside a
role="status"region, as in the release example. Static metadata does not need a live region. - Give isolated counts enough context, through surrounding text or an
aria-labelsuch as “3 open reviews”. - Decorative icons should accompany a meaningful text label. PureStack's Icon component hides its SVG from assistive technology by default.
- Keep labels short and check their contrast in the theme and variant you choose, especially when customizing semantic colors.
API reference
<Badge tone="success">Stable</Badge> A native <span> with two appearance props and one default slot. The default variant is surface. Static badges need no browser application.
Both props accept values or Regor refs. Register defineBadgeComponents() when using Badge in a browser application. Register the icon family separately if the default slot contains an Icon.
Appearance
tone
SemanticTone - Default
inherited
Selects a semantic palette. When omitted, Badge inherits the surrounding tone, which is neutral at the theme root. The label should explain the meaning even when colors cannot be distinguished.
neutralaccentfeaturesecondarycustomghostinfosuccesswarningdanger<Badge tone="success">Passing</Badge>
<Badge tone="warning">Review needed</Badge>
<Badge tone="danger">Failed</Badge>
<Badge tone="info">Queued</Badge> variant
ComponentVariant - Default
surface
Sets the visual treatment using the stateless form of the shared variants. A variant changes appearance; it never turns the badge into a link or a button.
solidsurfacesurfaceAltspotlightglassflatflatAltflatSolidoutlineFilloutlinesubtlesubtleBtnlinksheenunderlinerailbracketnone<Badge tone="accent" variant="solid">Featured</Badge>
<Badge tone="success">Stable</Badge>
<Badge variant="outline">v1.0</Badge> Badge always uses stateless variant styling. It does not expose a variantMode prop.
Slots and native attributes
Default slot
The badge's inline content: a label, a count, or text with an icon. The slot can contain reactive interpolation inside a Regor app.
Native attributes
id, class, title, and ARIA attributes reach the root span. Use ordinary attributes such as aria-label; Badge has no dedicated ariaLabel prop.
Events
Badge defines no custom events or interaction behavior. A clickable filter, removable tag, or navigation control should use an appropriate interactive component.
<Badge tone="info" aria-label="12 unread notifications">12</Badge> Reactive content
Bind tone or variant to refs and use interpolation for the label. When appearance is derived from other state, pass a computed ref so the component keeps updating.
The release checklist includes the full implementation: typed state, computed count and tone, a guarded publish action, and a live status region. Its TypeScript source tab is the exact file mounted by the preview.
For navigation, see BtnLink. For related action rows, see BtnGroup.