Notifications
In-app notification surfaces — a stacked feed panel, a single content card, a dense inbox row, and a corner count badge.
Notification Badge — A small count or status indicator overlaid on the top-end corner of any trigger (icon, avatar, button) — count, dot or ping modes in a neutral (default), primary, destructive or success tone. Pure CSS — no JavaScript.
Preview
<div class="flex min-h-40 w-full flex-wrap items-center justify-center gap-8 rounded-lg border border-border bg-card p-8">
{{-- A count on an icon button: the neutral tone is the default --}}
<x-ui.notification-badge :count="5">
<x-ui.button variant="outline" icon-label="{{ __('Notifications') }}">
<x-ui.icon name="bell" />
</x-ui.button>
</x-ui.notification-badge>
{{-- A clamped count in the destructive tone, with its own screen-reader text --}}
<x-ui.notification-badge :count="128" tone="destructive" :label="__(':count unread messages')">
<x-ui.button variant="outline" icon-label="{{ __('Messages') }}">
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16v12H5.2L4 17.5V4Z" /></svg>
</x-ui.button>
</x-ui.notification-badge>
{{-- A presence dot on a round avatar: `inset` puts it on the circle's edge --}}
<x-ui.notification-badge dot tone="success" ping inset :label="__('Online')">
<x-ui.avatar fallback="ZK" alt="Zoe Kim" />
</x-ui.notification-badge>
{{-- An unread dot on an icon button --}}
<x-ui.notification-badge dot>
<x-ui.button variant="outline" icon-label="{{ __('Inbox') }}">
<x-ui.icon name="bell" />
</x-ui.button>
</x-ui.notification-badge>
{{-- A count on a text button, opting into the brand colour --}}
<x-ui.notification-badge :count="3" tone="primary">
<x-ui.button variant="outline">{{ __('Inbox') }}</x-ui.button>
</x-ui.notification-badge>
</div>
Installation
php artisan ui:add notification-badge
Registry contract
php artisan ui:add notification-badge
writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.
-
resources/views/components/ui/notification-badge.blade.php
- Registry dependencies
- None — installs on its own.
- Packages
-
composer: jml/brok:^0.2
Use with AI
A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.
# Brok UI: Notification Badge (`notification-badge`)
A small count or status indicator overlaid on the top-end corner of any trigger (icon, avatar, button) — count, dot or ping modes in a neutral (default), primary, destructive or success tone. Pure CSS — no JavaScript.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add notification-badge
```
## Usage
```blade
<div class="flex min-h-40 w-full flex-wrap items-center justify-center gap-8 rounded-lg border border-border bg-card p-8">
{{-- A count on an icon button: the neutral tone is the default --}}
<x-ui.notification-badge :count="5">
<x-ui.button variant="outline" icon-label="{{ __('Notifications') }}">
<x-ui.icon name="bell" />
</x-ui.button>
</x-ui.notification-badge>
{{-- A clamped count in the destructive tone, with its own screen-reader text --}}
<x-ui.notification-badge :count="128" tone="destructive" :label="__(':count unread messages')">
<x-ui.button variant="outline" icon-label="{{ __('Messages') }}">
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16v12H5.2L4 17.5V4Z" /></svg>
</x-ui.button>
</x-ui.notification-badge>
{{-- A presence dot on a round avatar: `inset` puts it on the circle's edge --}}
<x-ui.notification-badge dot tone="success" ping inset :label="__('Online')">
<x-ui.avatar fallback="ZK" alt="Zoe Kim" />
</x-ui.notification-badge>
{{-- An unread dot on an icon button --}}
<x-ui.notification-badge dot>
<x-ui.button variant="outline" icon-label="{{ __('Inbox') }}">
<x-ui.icon name="bell" />
</x-ui.button>
</x-ui.notification-badge>
{{-- A count on a text button, opting into the brand colour --}}
<x-ui.notification-badge :count="3" tone="primary">
<x-ui.button variant="outline">{{ __('Inbox') }}</x-ui.button>
</x-ui.notification-badge>
</div>
```
## Props
- `count` (mixed|null, default `null`) — Numeric count to display; null with dot=false hides the badge.
- `dot` (bool, default `false`) — Renders a bare dot instead of a number.
- `max` (int, default `99`) — Counts above this clamp to "{max}+".
- `tone` (neutral|primary|destructive|success, default `neutral`) — Colour tone: neutral (a foreground dot or count, the default) keeps the marker calm so the brand colour stays free for the primary action and focus; primary opts into the brand fill; destructive and success are status tones. An unknown tone falls back to neutral.
- `ping` (bool, default `false`) — Adds an attention-seeking motion-safe ping ring.
- `position` (string, default `top-end`) — Corner placement: top-end or bottom-end.
- `inset` (bool, default `false`) — Set true when the child is round (an avatar) so the badge sits on the circle's edge instead of the square corner.
- `label` (string|null, default `null`) — Screen-reader text for the badge. Null keeps the translated default: "New notifications" for a dot, ":count notifications" for a number. A :count placeholder is replaced with the count.
## Use when
- Use to show state, status, notification, or lightweight social feedback near the relevant object.
- Overlaying a count, dot, or ping indicator on an icon, avatar, or button to signal unread items.
- Needing count clamping ("{max}+") or a bare attention dot without a number.
## Avoid when
- Do not overload small status elements with explanations that should remain visible in text.
- Showing a full notification with sender, message, and actions; use notification-card instead.
- Needing a persistent inline status label rather than a corner overlay; use badge instead.
## Anti-patterns
- Using color or icon alone to communicate state
## Rules
- Use the `<brok:notification-badge>` tag (or `<x-ui.notification-badge>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.
## Links
- Docs: https://brokui.dev/docs/components/notification-badge
- Registry JSON (files, props, contract): https://brokui.dev/r/open/notification-badge.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| count | mixed | null | null | Numeric count to display; null with dot=false hides the badge. |
| dot | bool | false | Renders a bare dot instead of a number. |
| max | int | 99 | Counts above this clamp to "{max}+". |
| tone | neutral | primary | destructive | success | neutral | Colour tone: neutral (a foreground dot or count, the default) keeps the marker calm so the brand colour stays free for the primary action and focus; primary opts into the brand fill; destructive and success are status tones. An unknown tone falls back to neutral. |
| ping | bool | false | Adds an attention-seeking motion-safe ping ring. |
| position | string | top-end | Corner placement: top-end or bottom-end. |
| inset | bool | false | Set true when the child is round (an avatar) so the badge sits on the circle's edge instead of the square corner. |
| label | string | null | null | Screen-reader text for the badge. Null keeps the translated default: "New notifications" for a dot, ":count notifications" for a number. A :count placeholder is replaced with the count. |
Slots
default— The trigger element (icon, avatar, or button) the badge overlays.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- The badge is hidden entirely unless dot is true or count is a positive number.
- Counts above max clamp to "{max}+"; the corner position mirrors under RTL via logical end placement.
- Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.
Guidance
Communicate concise object or system state.
Use when
- Use to show state, status, notification, or lightweight social feedback near the relevant object.
- Overlaying a count, dot, or ping indicator on an icon, avatar, or button to signal unread items.
- Needing count clamping ("{max}+") or a bare attention dot without a number.
Avoid when
- Do not overload small status elements with explanations that should remain visible in text.
- Showing a full notification with sender, message, and actions; use notification-card instead.
- Needing a persistent inline status label rather than a corner overlay; use badge instead.
Use instead
- Persistent explanatory text
Anti-patterns
- Using color or icon alone to communicate state
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- No component-owned keyboard interaction; native element behavior applies.
- Focus
none
- The numeric badge pairs a visible digit with a localized sr-only sentence (e.g. "3 notifications"); the dot variant announces "New notifications" instead. Pass label to name what the badge counts (for example "Unread messages" or ":count unread").
- The badge only adds decorative styling on the trigger; it does not change the trigger's own accessible name.
- Semantic HTML and a stable
data-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, so keyboard focus is always visible. - Disabled and invalid states are conveyed to assistive tech, not by color alone.
- Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
- Labels go through
__()and layout uses logical properties (ms-*,text-start), so it mirrors underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
Livewire can update this component through forwarded wire:* attributes.
Source
The exact, editable file ui:add writes
into your app. Previews render this same code; there are no preview-only components.
@props([
// Numeric count. Null + dot=false hides the badge entirely.
'count' => null,
// Render a bare dot instead of a number.
'dot' => false,
// Counts above this clamp to "{max}+".
'max' => 99,
// neutral (foreground dot) | primary | destructive | success
'tone' => 'neutral',
// Add an attention-seeking ping ring (motion-safe).
'ping' => false,
// top-end (default) | bottom-end — where the badge sits on the child.
'position' => 'top-end',
// true when the child is round (an avatar): the badge moves in to sit on
// the circle's edge instead of hanging off the square corner.
'inset' => false,
// Screen-reader text. Null keeps the translated default ("New notifications"
// for a dot, ":count notifications" for a number); `:count` is replaced.
'label' => null,
])
@php
// Neutral by default: an unread marker is a readout, so the brand
// colour stays free for the one primary action and focus.
$tones = [
'neutral' => 'bg-foreground text-background',
'primary' => 'bg-primary text-primary-foreground',
'destructive' => 'bg-destructive text-destructive-foreground',
'success' => 'bg-success text-success-foreground',
];
$tone = is_string($tone) && isset($tones[$tone]) ? $tone : 'neutral';
$toneClass = $tones[$tone];
$position = $position === 'bottom-end' ? 'bottom-end' : 'top-end';
$count = is_null($count) ? null : (int) $count;
$show = $dot || (! is_null($count) && $count > 0);
$display = (! is_null($count) && $count > (int) $max) ? $max.'+' : (string) $count;
$label = is_string($label) && trim($label) !== '' ? str_replace(':count', (string) $count, $label) : null;
// On a square child the badge straddles the corner (a third of it outside);
// on a round one it sits on the circle's edge. Logical `end` mirrors under RTL.
$place = match (true) {
$inset && $position === 'bottom-end' => 'bottom-0 end-0',
$inset => 'top-0 end-0',
$position === 'bottom-end' => 'bottom-0 end-0 translate-x-1/3 translate-y-1/3 rtl:-translate-x-1/3',
default => 'top-0 end-0 translate-x-1/3 -translate-y-1/3 rtl:-translate-x-1/3',
};
$badgeBase = 'pointer-events-none absolute z-10 inline-flex items-center justify-center rounded-full ring-2 ring-background';
$sizeClass = $dot
? 'size-2'
: 'h-4 min-w-4 px-1 text-2xs font-semibold leading-none tabular-nums';
@endphp
<span
data-slot="notification-badge"
data-tone="{{ $tone }}"
data-position="{{ $position }}"
{{ $attributes->merge(['class' => 'relative inline-flex']) }}
>
{{ $slot }}
@if ($show)
@if ($ping)
<span
aria-hidden="true"
class="pointer-events-none absolute z-0 inline-flex rounded-full opacity-60 motion-safe:animate-ping {{ $place }} {{ $dot ? 'size-2' : 'size-4' }} {{ $toneClass }}"
></span>
@endif
<span class="{{ $badgeBase }} {{ $place }} {{ $sizeClass }} {{ $toneClass }}">
@if ($dot)
<span class="sr-only">{{ $label ?? __('New notifications') }}</span>
@else
<span aria-hidden="true">{{ $display }}</span>
<span class="sr-only">{{ $label ?? trans_choice(':count notification|:count notifications', $count, ['count' => $count]) }}</span>
@endif
</span>
@endif
</span>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.3.1 - Status
- Stable
- License
-
open - Accessibility reviewed
- No review date recorded
- Last breaking change
- No date recorded
- Deprecation
- Not deprecated
- Contract
-
v6 - Foundation
-
≥ 1.0.0