Notification Badge

A counter or dot on an icon or avatar, cut out with CSS shape() so it reads on any background

Installation

pnpm dlx shadcn@latest add @alexandrejoly/notification-badge

Usage

import {  NotificationBadge,  NotificationBadgeContent,  NotificationBadgeIndicator,} from "@/components/ui/notification-badge";
<NotificationBadge value={3}>  <NotificationBadgeContent>    <Button aria-label="Notifications, 3 unread" size="icon" variant="outline">      <BellIcon />    </Button>  </NotificationBadgeContent>  <NotificationBadgeIndicator /></NotificationBadge>

Examples

Dot

A dot signals that something is new, without a number.

Counts

One character makes a circle, more make a pill, and the hole under it, the transparent cut-out around the badge, keeps the same shape. The demo caps the count at 99+ itself, since value shows whatever it gets.

Compact counts

value takes a string, so Intl.NumberFormat with notation: "compact" can shorten a large count to 1.2K. The demo pins "en" so the server and the browser format alike; pass the page’s locale instead. The label keeps the full number.

Beside a label

Beside a label, NotificationBadgeContent is left out and static takes the indicator out of the overlay.

Accessibility

The indicator is aria-hidden, since its count, read alone, doesn’t say what it counts. Put the count in the name of the button instead.

On an icon button, write it in the aria-label: “Notifications, 3 unread”, or “Messages, new activity” for a dot, which shows no count at all. Beside a label, keep the visible text first and add the rest in an sr-only span, so the name still starts with what the button shows.

Build the whole phrase with your translation library rather than joining pieces, and say “more than 99” where you show 99+.

A count that changes is not announced. To announce it, update one polite live region for the whole app with a phrase that says what changed, such as “2 new messages”.

In forced colors mode, such as Windows high contrast, the badge takes the system text color, so even a dot stays visible.

API reference

NotificationBadge

Shares the value with its parts. Sets the geometry of the hole, the transparent cut-out around the badge, as CSS variables.

Takes every prop of span.

Prop or variableTypeDefault
value

What the badge shows, as is: format it yourself, as 99+ or a compact number. Nothing shows for a number of 0 or below, or an empty string; a string such as "0" still shows. The badge widens by one step per character.

number | stringNone
variant

A counter, or a dot shown while value would show.

Default: "default"

"default" | "dot""default"
--hole-radius

The corner radius of the content, so the badge sits on its curve: 50% for an avatar.

Default: 0px

CSS length0px
--hole-gap

The transparent gap around the badge.

Default: 1.5px

CSS length1.5px
--hole-top, --hole-right

Moves the badge and its hole together, from the top and right edges.

CSS lengthNone

NotificationBadgeContent

Wraps the icon, button or avatar. Clips a hole under the badge so the gap shows whatever is behind it.

Takes every prop of span.

NotificationBadgeIndicator

A badge in the destructive color: a circle for one character, a pill for more, sized and placed from the root’s variables. Renders nothing while value is 0 or below, or empty, and is aria-hidden: see Accessibility. Add static to its class to sit it beside a label instead of over the content.

Takes every prop of span.

View as Markdown