Badges
Badges Examples
Badge/counter component that can be attached to an icon or any target node. Includes overlay/inline layouts, labels, pills, and interactive behavior.
Overlay basics (icon target)
Overlay places the badge on top of the target icon.
Live preview
Icon + quantity / dot / capped quantity
import Badges from "@/_components/badges/Badges";
export default function Example() {
return (
<>
<Badges
showInfo
baseNumber={12}
baseLimit={99}
iconName="Notifications"
variant="secondary"
/>
<Badges
showInfo
notQuantity
baseNumber={0}
baseLimit={0}
iconName="Mail"
variant="primary"
/>
<Badges
showInfo
baseNumber={120}
baseLimit={99}
iconName="Chat"
variant="danger"
/>
</>
);
}Inline (text targets)
Inline flows the badge next to the target. Useful for tabs, chip, filters, etc.
Live preview
Inline counters for text targets
import Badges from "@/_components/badges/Badges";
const targets = [
{ id: "inbox", label: "Inbox", count: 3 },
{ id: "alerts", label: "Alerts", count: 32 },
{ id: "updates", label: "Updates", count: 999 },
] as const;
export default function Example() {
return (
<>
{targets.map((t) => (
<Badges
key={t.id}
mode="inline"
target={t.label}
targetType="text"
showInfo
baseNumber={t.count}
baseLimit={99}
variant="danger"
/>
))}
</>
);
}Labels (start/end)
Use label + labelPosition for horizontal label placement.
Live preview
Label at start / end
import Badges from "@/_components/badges/Badges";
export default function Example() {
return (
<>
<Badges
showInfo
baseNumber={3}
baseLimit={9}
iconName="Notifications"
label="Alerts"
labelPosition="start"
variant="secondary"
/>
<Badges
showInfo
baseNumber={12}
baseLimit={99}
iconName="Mail"
label="Inbox"
labelPosition="end"
variant="primary"
/>
</>
);
}Sizes
Size affects badge offsets/tokens through CSS.
Live preview
Small / Medium / Large
import Badges from "@/_components/badges/Badges";
export default function Example() {
return (
<>
<Badges showInfo baseNumber={3} baseLimit={9} iconName="Notifications" size="small" />
<Badges showInfo baseNumber={3} baseLimit={9} iconName="Notifications" size="medium" />
<Badges showInfo baseNumber={3} baseLimit={9} iconName="Notifications" size="large" />
</>
);
}Variants
Variants control badge tone (and pill tone when pill is enabled).
Live preview
All variants
import Badges from "@/_components/badges/Badges";
export default function Example() {
return (
<>
<Badges showInfo baseNumber={1} baseLimit={9} iconName="Notifications" variant="primary" />
<Badges showInfo baseNumber={2} baseLimit={9} iconName="Notifications" variant="secondary" />
<Badges showInfo baseNumber={3} baseLimit={9} iconName="Notifications" variant="tertiary" />
<Badges showInfo baseNumber={4} baseLimit={9} iconName="Notifications" variant="success" />
<Badges showInfo baseNumber={8} baseLimit={9} iconName="Notifications" variant="warning" />
<Badges showInfo baseNumber={12} baseLimit={9} iconName="Notifications" variant="danger" />
<Badges showInfo baseNumber={99} baseLimit={9} iconName="Notifications" variant="info" />
</>
);
}Vertical layout (labelFlow)
For vertical layouts you can place the label above or below the target.
Live preview
labelFlow before vs after
import Badges from "@/_components/badges/Badges";
export default function Example() {
return (
<>
<Badges
iconName="Image"
label="Photos"
showInfo
baseNumber={999}
baseLimit={99}
orientation="vertical"
labelFlow="before"
variant="info"
/>
<Badges
iconName="Image"
label="Photos"
showInfo
baseNumber={999}
baseLimit={99}
orientation="vertical"
labelFlow="after"
variant="info"
/>
</>
);
}Pill mode: "target"
Wraps only the target in a pill. Common for "mobile" app-bar style items.
• Selection is external: control isSelected with your own state.
• Interactivity is inferred from onChange + disabled.
Live preview
Controlled selection (pill=target)
import { useState } from "react";
import Badges from "@/_components/badges/Badges";
export default function Example() {
const [selected, setSelected] = useState<"home" | "search" | "radio" | "alerts">("home");
return (
<>
<Badges
iconName="Home"
label="Home"
showInfo
baseNumber={3}
baseLimit={9}
orientation="vertical"
labelFlow="before"
pill="target"
variant="primary"
isSelected={selected === "home"}
onChange={() => setSelected("home")}
/>
<Badges
iconName="Search"
label="Browse"
showInfo={false}
baseNumber={0}
baseLimit={0}
orientation="vertical"
labelFlow="before"
pill="target"
variant="secondary"
isSelected={selected === "search"}
onChange={() => setSelected("search")}
/>
<Badges
iconName="Radio"
label="Radio"
showInfo
notQuantity
baseNumber={0}
baseLimit={0}
orientation="vertical"
labelFlow="after"
pill="target"
variant="tertiary"
isSelected={selected === "radio"}
onChange={() => setSelected("radio")}
/>
<Badges
iconName="Notifications"
label="Alerts"
showInfo
baseNumber={32}
baseLimit={99}
orientation="vertical"
labelFlow="after"
pill="target"
variant="danger"
isSelected={selected === "alerts"}
onChange={() => setSelected("alerts")}
/>
</>
);
}Pill mode: "all"
Wraps the whole anchor in a pill. Useful for fullscreen / horizontal navigation.
Live preview
Controlled selection (pill=all)
import { useState } from "react";
import Badges from "@/_components/badges/Badges";
export default function Example() {
const [selected, setSelected] = useState<"tasks" | "warnings" | "info" | "library">("tasks");
return (
<>
<Badges
iconName="Task"
label="Tasks"
labelPosition="end"
showInfo
baseNumber={12}
baseLimit={99}
pill="all"
variant="success"
isSelected={selected === "tasks"}
onChange={() => setSelected("tasks")}
/>
<Badges
iconName="Warning"
label="Warnings"
labelPosition="end"
showInfo
baseNumber={8}
baseLimit={9}
pill="all"
variant="warning"
isSelected={selected === "warnings"}
onChange={() => setSelected("warnings")}
/>
<Badges
iconName="Info"
label="Info"
labelPosition="end"
showInfo
baseNumber={1}
baseLimit={9}
pill="all"
variant="info"
isSelected={selected === "info"}
onChange={() => setSelected("info")}
/>
<Badges
iconName="LibraryBooks"
label="Library"
labelPosition="end"
showInfo={false}
baseNumber={0}
baseLimit={0}
pill="all"
variant="secondary"
isSelected={selected === "library"}
onChange={() => setSelected("library")}
/>
</>
);
}Custom target (ReactNode)
When target is provided, it takes priority over iconName.
Live preview
Target as ReactNode
import Badges from "@/_components/badges/Badges";
import Icon from "@/_components/icon/Icon";
export default function Example() {
return (
<>
<Badges
mode="overlay"
target={
<span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
<Icon name="Flight" size="small" />
<span>Trips</span>
</span>
}
targetType="text"
variant="warning"
baseNumber={32}
baseLimit={99}
showInfo
pill="all"
onChange={() => {}}
/>
<Badges
mode="inline"
target={<span>Plain text target</span>}
targetType="text"
variant="info"
baseNumber={1}
baseLimit={9}
showInfo
notQuantity
/>
</>
);
}Interactivity (controlled)
When you pass onChange (and not disabled), the root becomes keyboard-focusable and triggers on Click / Enter / Space.
Live preview
Toggle dot vs count (controlled)
import { useState } from "react";
import Badges from "@/packages/ui/src/_components/badges/Badges";
export default function Example() {
const [enabled, setEnabled] = useState(true);
return (
<Badges
showInfo
iconName="Notifications"
label={enabled ? "Notifications: ON" : "Notifications: OFF"}
labelPosition="end"
notQuantity={!enabled}
baseNumber={enabled ? 3 : 0}
baseLimit={9}
variant="secondary"
onChange={() => setEnabled((v) => !v)}
/>
);
}Edge cases (numbers)
Use these cases to stress-test rendering and caps.
Numbers edge cases
import Badges from "@/_components/badges/Badges";
export default function Example() {
return (
<>
<Badges showInfo iconName="Notifications" baseNumber={0} baseLimit={9} />
<Badges showInfo iconName="Notifications" baseNumber={-1} baseLimit={9} />
<Badges showInfo iconName="Notifications" baseNumber={9} baseLimit={0} />
<Badges showInfo iconName="Mail" baseNumber={10} baseLimit={9} />
<Badges showInfo iconName="Mail" baseNumber={120} baseLimit={99} />
</>
);
}On this page
Badges Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| showInfo | boolean | Yes | — | Enables rendering the badge node. When false, no badge/dot is rendered. |
| notQuantity | boolean | No | false | If true, renders a small dot instead of a numeric counter. |
| baseNumber | number | Yes | — | Current value for the counter (used when notQuantity is false). |
| baseLimit | number | Yes | — | Max visible value. If baseNumber > baseLimit, it renders `${baseLimit}+`. |
| iconName | string | No | — | Renders an Icon as the target. Ignored if target is provided. |
| target | React.ReactNode | No | — | Custom target node (e.g., text, avatar, etc.). Takes priority over iconName. |
| targetType | "icon" | "text" | No | — | If not provided, inferred: "icon" if iconName exists, else "text" if target exists, else "icon". |
| size | "small" | "medium" | "large" | No | "medium" | Size preset (affects overlay offsets/tokens via CSS). |
| variant | "primary" | "secondary" | "tertiary" | "success" | "warning" | "danger" | "info" | No | — | Badge tone. Also used as pill tone when pill is enabled. |
| mode | "overlay" | "inline" | No | "overlay" | Overlay places the badge on top of the target. Inline flows next to it. |
| label | string | No | — | Optional label text. |
| labelPosition | "start" | "end" | No | — | Label placement for horizontal layout (only when label is provided). |
| orientation | "horizontal" | "vertical" | No | "horizontal" | Stack direction for label/target layout. |
| labelFlow | "after" | "before" | No | "after" | For vertical layouts: before puts the label above the target. |
| pill | "none" | "target" | "all" | No | "none" | Enables pill styling: wrap only the target or the whole anchor. |
| isSelected | boolean | No | false | Adds selected modifier (useful with pill styling). |
| disabled | boolean | No | false | Disables interactivity and sets aria-disabled. |
| onChange | (...args: any) => any | void | No | — | When provided (and not disabled), the badge becomes interactive: role='button', tabIndex=0, click + Enter/Space. |
| interactive | boolean | No | — | Currently not used by the component (interactivity is inferred from onChange + disabled). |
| className | string | No | — | Extra classes for the root. |
Components