A versatile badge component for displaying status, categories, or small pieces of information. Supports multiple tone states and can be styled with different emphasis levels for various use cases. Compose with Group to build multi-section chips out of two or more badges (and optionally a trailing button) without any badge-specific plumbing.
The emphasis prop controls the visual style of the badge. primary uses a solid accent surface with high-contrast text; secondary (default) uses a tone-tinted soft surface; tertiary uses a neutral surface with bold tone-colored text; adaptive inherits ambient text color and tints from the surrounding context.
---
// GENERATED from packages/demos/badge/emphasis.demo.tsx — edit the source, not this file.
import Badge from "@pindoba/astro-badge";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge emphasis="primary">Primary</Badge>
<Badge emphasis="secondary">Secondary</Badge>
<Badge emphasis="tertiary">Tertiary</Badge>
<Badge emphasis="adaptive">Adaptive</Badge>
</div>Badges support semantic tone colors for communicating status at a glance. A color family (teal, cyan, violet, pink and the tinted neutrals slate, stone, sage, mauve) is accepted too: it colors without implying a status — use it for labels, people and categories, and keep the semantic tones for status.
---
// GENERATED from packages/demos/badge/tone.demo.tsx — edit the source, not this file.
import Badge from "@pindoba/astro-badge";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "md", direction: "column" })}>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge tone="primary" emphasis="primary">
Primary
</Badge>
<Badge tone="success" emphasis="primary">
Success
</Badge>
<Badge tone="warning" emphasis="primary">
Warning
</Badge>
<Badge tone="danger" emphasis="primary">
Danger
</Badge>
<Badge tone="neutral" emphasis="primary">
Neutral
</Badge>
</div>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge tone="primary">Primary</Badge>
<Badge tone="success">Success</Badge>
<Badge tone="warning">Warning</Badge>
<Badge tone="danger">Danger</Badge>
<Badge tone="neutral">Neutral</Badge>
</div>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge tone="primary" emphasis="tertiary">
Primary
</Badge>
<Badge tone="success" emphasis="tertiary">
Success
</Badge>
<Badge tone="warning" emphasis="tertiary">
Warning
</Badge>
<Badge tone="danger" emphasis="tertiary">
Danger
</Badge>
<Badge tone="neutral" emphasis="tertiary">
Neutral
</Badge>
</div>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge tone="teal">Teal</Badge>
<Badge tone="cyan">Cyan</Badge>
<Badge tone="violet">Violet</Badge>
<Badge tone="pink">Pink</Badge>
<Badge tone="slate">Slate</Badge>
<Badge tone="stone">Stone</Badge>
<Badge tone="sage">Sage</Badge>
<Badge tone="mauve">Mauve</Badge>
</div>
</div>Four size variants are available to fit different layout contexts.
---
// GENERATED from packages/demos/badge/size.demo.tsx — edit the source, not this file.
import Badge from "@pindoba/astro-badge";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge size="sm">Small</Badge>
<Badge size="md">Medium</Badge>
</div>Badge inherits panel’s full surface model. background, border, and translucent work across every emphasis × tone combination — panel’s compound variants remap each surface.* value to the correct ramp (accent under primary, palette-tinted under secondary, neutral under tertiary).
---
// GENERATED from packages/demos/badge/background.demo.tsx — edit the source, not this file.
import Badge from "@pindoba/astro-badge";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "md", direction: "column" })}>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge tone="primary" emphasis="primary" background="surface.peak">
peak
</Badge>
<Badge tone="primary" emphasis="primary" background="surface.hill">
hill
</Badge>
<Badge tone="primary" emphasis="primary" background="surface.base">
base
</Badge>
<Badge tone="primary" emphasis="primary" background="surface.valley">
valley
</Badge>
<Badge tone="primary" emphasis="primary" background="surface.ground">
ground
</Badge>
<Badge tone="primary" emphasis="primary" background="transparent">
transparent
</Badge>
</div>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge tone="primary" emphasis="secondary" background="surface.peak">
peak
</Badge>
<Badge tone="primary" emphasis="secondary" background="surface.hill">
hill
</Badge>
<Badge tone="primary" emphasis="secondary" background="surface.base">
base
</Badge>
<Badge tone="primary" emphasis="secondary" background="surface.valley">
valley
</Badge>
<Badge tone="primary" emphasis="secondary" background="surface.ground">
ground
</Badge>
<Badge tone="primary" emphasis="secondary" background="transparent">
transparent
</Badge>
</div>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge tone="primary" emphasis="tertiary" background="surface.peak">
peak
</Badge>
<Badge tone="primary" emphasis="tertiary" background="surface.hill">
hill
</Badge>
<Badge tone="primary" emphasis="tertiary" background="surface.base">
base
</Badge>
<Badge tone="primary" emphasis="tertiary" background="surface.valley">
valley
</Badge>
<Badge tone="primary" emphasis="tertiary" background="surface.ground">
ground
</Badge>
<Badge tone="primary" emphasis="tertiary" background="transparent">
transparent
</Badge>
</div>
</div>Embed icons inside the badge content slot to reinforce meaning alongside the status label. Use 1em for icon sizes so they scale naturally with the badge’s size prop.
---
// GENERATED from packages/demos/badge/with-icons.demo.tsx — edit the source, not this file.
import Badge from "@pindoba/astro-badge";
import { CircleCheck, TriangleAlert, CircleX, Info, Lock } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "xl", direction: "column" })}>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge tone="success">
<CircleCheck /> Deployed
</Badge>
<Badge tone="warning">
<TriangleAlert /> Pending
</Badge>
<Badge tone="danger">
<CircleX /> Failed
</Badge>
<Badge tone="primary">
<Info /> Info
</Badge>
</div>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge tone="success" emphasis="secondary">
<CircleCheck /> Active
</Badge>
<Badge tone="warning" emphasis="secondary">
<TriangleAlert /> Review
</Badge>
<Badge tone="danger" emphasis="secondary">
<CircleX /> Blocked
</Badge>
<Badge tone="neutral" emphasis="secondary">
<Lock /> Private
</Badge>
</div>
</div>Badges host Stamp addons through the same leading / trailing slots buttons use. The slot renders an affix box and the badge’s content area publishes the Control Var Contract’s --affix-* vocabulary, so a <Stamp emphasis="ghost"> snaps to a deterministic square matched to the badge’s size — identical composition, one tier smaller. Customize the box via passThrough.leading / passThrough.trailing (e.g. { props: { decorative: true } }).
---
// GENERATED from packages/demos/badge/with-stamps.demo.tsx — edit the source, not this file.
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import {
CircleCheck,
TriangleAlert,
Sparkles,
ArrowRight,
} from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "xl", direction: "column" })}>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge tone="success">
<Stamp slot="leading" emphasis="ghost">
<CircleCheck />
</Stamp>
Deployed
</Badge>
<Badge tone="warning" emphasis="secondary">
<Stamp slot="leading" emphasis="ghost">
<TriangleAlert />
</Stamp>
Review
</Badge>
<Badge tone="primary" emphasis="primary">
<Stamp slot="leading" emphasis="ghost">
<Sparkles />
</Stamp>
New
</Badge>
</div>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge size="xs" tone="neutral">
<Stamp slot="trailing" emphasis="ghost">
<ArrowRight />
</Stamp>
Details
</Badge>
<Badge size="sm" tone="neutral">
<Stamp slot="trailing" emphasis="ghost">
<ArrowRight />
</Stamp>
Details
</Badge>
<Badge size="md" tone="primary" emphasis="secondary">
<Stamp slot="leading" emphasis="secondary">
<Sparkles />
</Stamp>
Accented stamp
</Badge>
</div>
</div>Buttons placed inside a badge are automatically resized to fit the badge’s dimensions. You don’t need to set a size prop on the button; it scales proportionally with the badge’s own size variant. Use shape="circle" emphasis="ghost" for a compact dismiss target that inherits the badge’s color context without adding visual noise.
To opt out of this behavior, set buttonFit={false} on the badge — buttons will then render at their own default size.
---
// GENERATED from packages/demos/badge/with-buttons.demo.tsx — edit the source, not this file.
import Badge from "@pindoba/astro-badge";
import Button from "@pindoba/astro-button";
import { X } from "@lucide/astro";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "xl", direction: "column" })}>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge size="sm" tone="neutral" emphasis="secondary">
sm badge{" "}
<Button aria-label="Dismiss" shape="circle" emphasis="ghost">
<X />
</Button>
</Badge>
<Badge size="md" tone="neutral" emphasis="secondary">
md badge{" "}
<Button aria-label="Dismiss" shape="circle" emphasis="ghost">
<X />
</Button>
</Badge>
</div>
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge tone="primary">
New feature{" "}
<Button aria-label="Dismiss" shape="circle" emphasis="ghost">
<X />
</Button>
</Badge>
<Badge tone="success">
Deployed{" "}
<Button aria-label="Dismiss" shape="circle" emphasis="ghost">
<X />
</Button>
</Badge>
<Badge tone="warning">
Pending{" "}
<Button aria-label="Dismiss" shape="circle" emphasis="ghost">
<X />
</Button>
</Badge>
<Badge tone="danger">
Failed{" "}
<Button aria-label="Dismiss" shape="circle" emphasis="ghost">
<X />
</Button>
</Badge>
<Badge tone="neutral">
Archived{" "}
<Button aria-label="Dismiss" shape="circle" emphasis="ghost">
<X />
</Button>
</Badge>
</div>
</div>Wrap two or more badges in a Group to build multi-section chips. Group owns the outer outline and emits dividers between children, so badges (with different emphasis or tone) — and even a trailing Button — visually merge into a single unit. This replaces the previous label slot. See the Group page for the live demo.
Use passThrough to apply custom Panda CSS styles or HTML attributes to any slot (root, content).
---
// GENERATED from packages/demos/badge/custom.demo.tsx — edit the source, not this file.
import Badge from "@pindoba/astro-badge";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "md",
direction: "row",
align: "center",
flexWrap: "wrap",
})}
>
<Badge
tone="danger"
passThrough={{
root: { style: { letterSpacing: "wider" } },
}}
>
Custom
</Badge>
<Badge
tone="primary"
passThrough={{
content: {
style: { fontWeight: "bold", textTransform: "uppercase" },
},
}}
>
Bold
</Badge>
<Badge
tone="success"
passThrough={{
root: {
props: { role: "status", "aria-label": "Success status" },
},
}}
>
Accessible
</Badge>
</div>