Components Badges

Petal Pro is the full SaaS app this is built for

Auth, billing, admin, and Claude Code integration included. One purchase, unlimited projects.

Badges

A small label for counts, statuses and categories.
Variants

A small label for counts, statuses and categories. light stays light in both colour schemes, soft adapts to dark mode, dark is maximum emphasis, outline stays quiet on any surface.

Light Soft Dark Outline
heex
<div class="flex flex-wrap items-center justify-center gap-3">
<.badge label="Light" />
<.badge variant="soft" label="Soft" />
<.badge variant="dark" label="Dark" />
<.badge variant="outline" label="Outline" />
</div>
Semantic colours

The full colour range in the soft variant - primary and secondary follow your theme dials, the semantic four carry meaning, gray labels without shouting.

primary secondary info success warning danger gray
heex
<div class="flex flex-wrap items-center justify-center gap-3">
<.badge
:for={c <- ~w(primary secondary info success warning danger gray)}
color={c}
variant="soft"
label={c}
/>
</div>
Status dots

dot puts a filled circle in front of the label, the convention every status table already uses. It takes the badge's own colour, so the row scans on colour alone from across the desk. The dot is decorative - screen readers get the word, not the circle - so keep the label carrying the meaning.

Active Pending Failed Archived
heex
<div class="flex flex-wrap items-center justify-center gap-3">
<.badge dot color="success" variant="soft" label="Active" />
<.badge dot color="warning" variant="soft" label="Pending" />
<.badge dot color="danger" variant="soft" label="Failed" />
<.badge dot color="gray" variant="outline" label="Archived" />
</div>
Neutral chips, semantic dots

dot_color unpins the dot from the badge, so the chip stays quiet and the circle carries the state. Reach for the neutral chip when the label is what varies and the same few states repeat behind it; reach for the coloured chip above when the state is the message and the label is only naming it.

Production Staging Preview
heex
<div class="flex flex-wrap items-center justify-center gap-3">
<.badge color="gray" variant="outline" dot dot_color="success">Production</.badge>
<.badge color="gray" variant="outline" dot dot_color="warning">Staging</.badge>
<.badge color="gray" variant="outline" dot dot_color="danger">Preview</.badge>
</div>
Sizes

Five sizes on the shared scale; with_icon tightens the padding for a leading glyph.

xs sm md lg xl
heex
<div class="flex flex-wrap items-center justify-center gap-3">
<.badge :for={z <- ~w(xs sm md lg xl)} size={z} label={z} />
</div>
Properties
Attribute Type Default Description
class any nil CSS class for parent div
color string "primary"
one of: "primary", "secondary", "info", "success", "warning", "danger", "gray"
dot boolean false renders a small filled circle before the label - the status-badge convention. The dot rides the badge's own colour and is decorative (aria-hidden), so the label has to carry the meaning on its own. Renders before an icon when both are set.
dot_color string nil colours the dot independently of the badge. nil, the default, inherits the badge's colour exactly as before. Set it for the neutral chip - quiet chrome, one semantic dot: `<.badge color="gray" variant="outline" dot dot_color="success">Production</.badge>`. The stop matches what an inherited dot of that colour shows on the same variant, so the override moves the hue and nothing else. The `dark` variant is the exception - its inherited dot is currentColor, which no ramp can match - so an override there takes the 400 stop, and is a visible change even when it names the badge's own colour. Keep it to that shape: when the badge already carries a semantic colour of its own, leave the dot inherited rather than putting two meanings in one chip - a success badge with a danger dot reads as neither.
one of: nil, "primary", "secondary", "info", "success", "warning", "danger", "gray"
label string nil label your badge
rest global
role string "note"
one of: "note", "status"
size string "md"
one of: "xs", "sm", "md", "lg", "xl"
variant string "light"
one of: "light", "dark", "soft", "outline"
with_icon boolean false adds some icon base classes
:inner_block slot slot