Skip to content
Brok UI

Loading…

No results

Notifications

Open source

In-app notification surfaces — a stacked feed panel, a single content card, a dense inbox row, and a corner count badge.

Version
v1.3.1
Stability
stable
License
MIT
Related
Notification Panel
Notification Card

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

5 notifications 128 unread messages ZK Zoe Kim Online New notifications 3 notifications
Tone
previews.components.notification-badge.default.blade.php 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>
Neutral Current
Primary Current
Destructive Current
Success Current

Installation

terminal
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.

  • blade 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.

notification-badge.md
# 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

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
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.

notification-badge

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

Status and lightweight feedback

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
root
Theming hooks
notification-badge

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
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-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, 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 under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Safe

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.

resources/views/components/ui/notification-badge.blade.php Blade
@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