Skip to main content
Urbicon UI
source

NotificationBadge

An unread-count badge that renders nothing when the count is 0.

01 Usage

Basic

Notifications 3
<script lang="ts">
  import { NotificationBadge } from '@urbicon-ui/auth';
  import { Button } from '@urbicon-ui/blocks';

  let count = $state(3);
  let lastClick = $state<number | null>(null);
</script>

<div class="flex items-center gap-6">
  <div class="relative">
    <span class="text-text-secondary text-sm">Notifications</span>
    <!-- Bewusst kein `alert()`: Das Beispiel läuft auch außerhalb der
         Doku-Seite (Landing-Hero), und ein Browser-Dialog blockiert dort alles
         andere. Die Rückmeldung steht daneben. -->
    <NotificationBadge {count} onclick={() => (lastClick = count)} />
  </div>

  {#if lastClick !== null}
    <span class="text-text-tertiary text-sm" role="status">{lastClick} unread</span>
  {/if}

  <div class="flex gap-2">
    <Button size="sm" variant="outlined" intent="neutral" onclick={() => count++}>+1</Button>
    <Button
      size="sm"
      variant="outlined"
      intent="neutral"
      onclick={() => (count = Math.max(0, count - 1))}>-1</Button
    >
    <Button size="sm" variant="ghost" intent="neutral" onclick={() => (count = 0)}>Clear</Button>
  </div>
</div>

02 Accessibility

Nothing is rendered at zero

The badge only exists while count > 0. At zero it renders nothing at all, so a keyboard user tabbing past a quiet bell moves straight to the next control instead of stopping on an empty badge.

The handler decides what it is

With an onclick the badge is a role="button" with tabindex="0", activated by Enter or Space, and it carries the pressable styling — pointer cursor, hover and active scale. Without one it is a role="status", a polite live region: it stays out of the tab order rather than being a focus stop on which every key is dead, and it drops the pressable styling with the semantics, so a decorative count does not look clickable. The role prop narrows what is announced; it does not make the badge operable, so role="button" without a handler is a button nothing can activate — pass an onclick for a button.

It names itself

The visible content is 3 or 99+, and the accessible name is the localized notifications.badge.unread with that same text substituted — "Unread notifications: 3" — so the name is never a bare number. Past the cap it says 99+ too, not the real count: a voice-control user can only say the label they can read. Your own aria-label wins over it, as does a t override of the string. The status region exists from the first unread on — at zero the badge renders nothing at all — so a change between two non-zero counts happens inside a region that was already there, while the first unread arrives together with it.

03 API Reference

9 props 1 required
Prop
Type
Default
Description

04 Types

Local type definitions used by this component.

4 types
Name
Kind
Category
Used by
Description

05 Installation

Import

import { NotificationBadge } from '@urbicon-ui/auth';