ComponentsMisc
Overlay badge
Pins a count or a dot to the corner of an icon, a button or an avatar, and says it in words.
Import
import { OverlayBadge } from "@booleanpress/ui/overlay-badge"Usage
Wrap one element. The badge sits on its top-end corner (top-left in a right-to-left page), with a 2 px ring of the card colour around it, so it stands clear of what it covers.
import { BellIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { OverlayBadge } from "@booleanpress/ui/overlay-badge"
export function Alerts() {
return (
<OverlayBadge count={3} severity="danger" label="3 failed deliveries">
<Button variant="outline" size="icon" aria-label="Delivery alerts">
<BellIcon />
</Button>
</OverlayBadge>
)
}count, max, dot, severity and size go to the Badge; a count without a severity takes the primary fill. label is required: the badge in words, such as "3 failed deliveries". The drawn badge is hidden from assistive technology and the label is read instead, so write the label to stand on its own. When the count changes, change the label with it.
Examples
Icon
A count or a dot on a 24 px icon.
import { BellIcon, CalendarIcon, MailIcon } from "lucide-react"
import { OverlayBadge } from "@booleanpress/ui/overlay-badge"
export default function OverlayBadgeIcon() {
return (
<div className="flex items-center justify-center gap-8">
<OverlayBadge count={2} label="2 new notifications">
<BellIcon className="size-6 text-foreground" aria-hidden />
</OverlayBadge>
<OverlayBadge count={4} severity="danger" label="4 bookings to confirm">
<CalendarIcon className="size-6 text-foreground" aria-hidden />
</OverlayBadge>
<OverlayBadge dot label="New messages">
<MailIcon className="size-6 text-foreground" aria-hidden />
</OverlayBadge>
</div>
)
}Avatar
A count, or a status dot, on an avatar.
import { Avatar, AvatarFallback } from "@booleanpress/ui/avatar"
import { OverlayBadge } from "@booleanpress/ui/overlay-badge"
export default function OverlayBadgeAvatar() {
return (
<div className="flex items-center justify-center gap-8">
<OverlayBadge count={4} severity="danger" label="4 unassigned tickets">
<Avatar size="lg">
<AvatarFallback>AL</AvatarFallback>
</Avatar>
</OverlayBadge>
<OverlayBadge dot severity="success" label="Online">
<Avatar size="lg">
<AvatarFallback>GH</AvatarFallback>
</Avatar>
</OverlayBadge>
</div>
)
}Dot
dot puts an 8 px dot on a button; the button is described by the label.
import { BellIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { OverlayBadge } from "@booleanpress/ui/overlay-badge"
export default function OverlayBadgeDot() {
return (
<div className="flex items-center justify-center gap-8">
<OverlayBadge dot severity="info" label="New delivery alerts">
<Button variant="outline" size="icon" aria-label="Delivery alerts">
<BellIcon />
</Button>
</OverlayBadge>
<OverlayBadge dot severity="danger" label="A sending domain failed verification">
<Button variant="ghost">Domains</Button>
</OverlayBadge>
</div>
)
}Max
max caps the count: 99+, 999+.
import { InboxIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { OverlayBadge } from "@booleanpress/ui/overlay-badge"
export default function OverlayBadgeMax() {
return (
<div className="flex items-center justify-center gap-10">
<OverlayBadge count={128} max={99} severity="danger" label="128 failed emails">
<InboxIcon className="size-6 text-foreground" aria-hidden />
</OverlayBadge>
<OverlayBadge count={1250} max={999} label="1,250 open tickets">
<Button variant="outline">Tickets</Button>
</OverlayBadge>
</div>
)
}Accessibility
- Semantics
- A
spanaround the element, which keeps its own role. The badge isaria-hidden; the label is a visually hidden text with an id, and a focusable element getsaria-describedbypointing to it. - Labels
- A focusable element (a button, a link) is described by the label: a screen reader says its name, then the label ("Delivery alerts, button, 3 failed deliveries"), and the label is hidden so it is not read twice. Any other element (an icon, an avatar) has the label read after it in the page.
- Focus
- The wrapper is not focusable; the wrapped element keeps its own focus and keys.
- Known limits
- Whether the element is focusable is checked once it is in the page, so the label is read beside it until the page has loaded.
- A changed count is not announced. For a count that changes while people watch, announce it in a live region the page owns.
- The badge covers the element's corner. Keep at least 10 px of space around the element so the badge does not cover its neighbours.
Keyboard
| Key | Behaviour |
|---|
API
OverlayBadge
Renders a span and passes it every other prop.
| Prop | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactElement<{ "aria-describedby"?: string | undefined; }, string | JSXElementConstructor<any>> | One element: an icon, a button or an avatar. | |
labelrequired | string | The badge in words, such as "3 unread messages". A focusable child is described by it; any other child has it read after it. The badge itself is hidden from assistive technology. | |
count | number | A number to show, in the provider's locale: the badge becomes a round count. | |
dot | boolean | false | An 8px dot instead of a count. |
max | number | The largest count shown; above it the badge reads {max}+ (the provider's badgeOverflow). | |
severity | "secondary" | "success" | "info" | "warning" | "help" | "danger" | "contrast" | "primary" | null | The badge's fill: primary (default), secondary, success, info, warning, help, danger or contrast. | |
size | "default" | "sm" | "lg" | null | The badge's size: sm, default or lg. |
Every part takes className, merged with its defaults by cn(), and ref, which reaches the element it renders.
Data attributes: data-slot="overlay-badge" (OverlayBadge).
Theming
The badge takes the solid tokens of its severity (see Badge). The ring is --card, the surface examples and cards use; on another surface, set it with className on the wrapper ([&_[data-slot=overlay-badge-badge]]:outline-background).
The tokens its classes read. Change their values in your theme, and it follows.
| Token | Used for |
|---|---|
--card | outline |