Skip to content

shadcn.io is an independent project built on shadcn/ui

Free

Notifications Popover for Vue

A bell with a badge of 3 that opens Notifications, Mark all as read, and three entries with their times.

UI library: Ark UI

A bell button carries a badge of 3. It opens Notifications, a Mark all as read button, and three rows: New comment, New follower, and Update available, with their times. Mark all as read does not clear the list.

Use it to show recent activity without leaving the page. Wire Mark all as read yourself if the badge should drop. A full history belongs in a Dialog. In Vue, Ark UI does not mark the items read.

Preview

Preview width

Installation

MCP · Pro
Make your AI a shadcn expert
Your agent installs components, builds pages and restyles your app from one prompt, in Claude Code, Cursor or VS Code.
Part of Pro

Anatomy

Popover anatomy: trigger, title, positioner, arrow, description, content

Usage

<Popover>
  <PopoverTrigger :asChild="true">
    <Button
      :aria-label="`Notifications, ${notifications.length} unread`"
      size="icon-md"
      variant="ghost"
    >
      <BellIcon aria-hidden="true" class="size-5" />
      <Badge class="absolute -end-1 -top-1" :pill="true" size="sm">
        {{ notifications.length }}
      </Badge>
    </Button>
  </PopoverTrigger>
  <PopoverContent class="w-80 max-w-full gap-3">
    <div class="flex items-center justify-between gap-2">
      <PopoverTitle>Notifications</PopoverTitle>
      <Button size="sm" variant="ghost">Mark all as read</Button>
    </div>
    <Separator />
    <ul class="flex flex-col gap-2">
      <li v-for="notification in notifications" :key="notification.title">
        <p class="font-medium">{{ notification.title }}</p>
        <p class="text-muted-foreground text-xs">{{ notification.time }}</p>
      </li>
    </ul>
  </PopoverContent>
</Popover>

FAQ

What does each notification row show?

notifications has three entries. Each li has a font-medium title and a text-muted-foreground text-xs time: New comment, New follower, and Update available. The list is a ul with flex flex-col gap-2.

How does the unread badge get its number?

The Badge renders notifications.length, which is 3. It uses pill, size="sm", and className="absolute -end-1 -top-1". The button aria-label is Notifications, then that count, then unread.

How does the bell become the trigger?

The trigger is a Button with variant="ghost" and size="icon-md". BellIcon has aria-hidden="true" and className="size-5". The badge is the other child of that button. Activating the button opens the panel. Set asChild on PopoverTrigger and pass that one button. PopoverContent is the panel.

What is in the panel header?

A justify-between row holds PopoverTitle reading Notifications and a ghost Button with size="sm" reading Mark all as read. A Separator sits under that row. PopoverContent is w-80 max-w-full with gap-3. This demo gives the mark-read button no handler yet.

Do the notifications update?

The array is a constant: three titles and the times 2 minutes ago, 1 hour ago, and 3 hours ago. The badge count is that array's length. Mark all as read has no handler in this file.

How does this panel dismiss?

Escape closes the panel and returns focus to the trigger. A click outside closes it too. The panel sits beside the button, and the rest of the page stays usable.

API Reference

Popover

Sets asChild. The rest is on the Popover page.

Button

Sets aria-label="`Notifications, ${notifications.length} unread`", size="icon-md", variant="ghost" and size="sm". The rest is on the Button page.

Badge

Sets pill and size="sm". The rest is on the Badge page.

Separator

No extra props in this demo. The rest is on the Separator page.