# Notifications Sheet Panel

> Notifications sheet opened from a bell trigger, with icon tiles, unread highlighting, timestamps, an unread count, and a mark all read action.

A shadcn/ui block from the [@7ovr](https://7ovr.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/7ovr/notifications-1).

- Page, with a live preview: https://shadcn.exchange/r/7ovr/notifications-1
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/7ovr/notifications-1-light-44363794af7a54be.png
- Registry item: https://7ovr.com/r/notifications-1.json
- Categories: notifications
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://7ovr.com/r/notifications-1.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@base-ui/react`
- Registry items: `button`, `scroll-area`, `separator`, `sheet`

## Notes from the registry

Requires a `base-*` style in components.json (the `shadcn init` default). Legacy `new-york`/`radix-*` styles install Radix primitives, which reject the `render` prop this block uses.

## Files

### `registry/blocks/notifications/1/notifications-block.tsx`

```tsx
"use client"

import * as React from "react"
import { Button } from "@/components/ui/button"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Separator } from "@/components/ui/separator"
import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"
import { cn } from "@/lib/utils"
import { IconPlaceholder } from "@/components/icons/icon-placeholder"

type IconProps = { className?: string; size?: number | string }

const initialNotifications = [
  {
    id: 1,
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="UserPlus"
        tabler="IconUserPlus"
        hugeicons="UserAddIcon"
        phosphor="UserPlus"
        remixicon="RiUserAddLine"
        {...p}
      />
    ),
    title: "New team member joined",
    body: "Sarah Chen accepted your invitation and joined the workspace.",
    timestamp: "2 Min Ago",
    unread: true,
  },
  {
    id: 2,
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="GitMerge"
        tabler="IconGitMerge"
        hugeicons="GitMergeIcon"
        phosphor="GitMerge"
        remixicon="RiGitMergeLine"
        {...p}
      />
    ),
    title: "Pull request merged",
    body: "feat/dashboard-v2 was merged into main by Jordan Lee.",
    timestamp: "14 Min Ago",
    unread: true,
  },
  {
    id: 3,
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="CircleAlert"
        tabler="IconAlertCircle"
        hugeicons="AlertCircleIcon"
        phosphor="WarningCircle"
        remixicon="RiErrorWarningLine"
        {...p}
      />
    ),
    title: "Usage limit warning",
    body: "Your team has used 85% of the monthly API quota.",
    timestamp: "1 Hour Ago",
    unread: true,
  },
  {
    id: 4,
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="FileText"
        tabler="IconFileText"
        hugeicons="File01Icon"
        phosphor="FileText"
        remixicon="RiFileTextLine"
        {...p}
      />
    ),
    title: "Invoice generated",
    body: "Invoice #INV-2024-0118 for $1,240.00 is ready to download.",
    timestamp: "3 Hours Ago",
    unread: false,
  },
  {
    id: 5,
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="ShieldCheck"
        tabler="IconShieldCheck"
        hugeicons="Shield01Icon"
        phosphor="ShieldCheck"
        remixicon="RiShieldCheckLine"
        {...p}
      />
    ),
    title: "Security scan complete",
    body: "Weekly vulnerability scan finished, no critical issues found.",
    timestamp: "Yesterday",
    unread: false,
  },
  {
    id: 6,
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="Megaphone"
        tabler="IconSpeakerphone"
        hugeicons="MegaphoneIcon"
        phosphor="Megaphone"
        remixicon="RiMegaphoneLine"
        {...p}
      />
    ),
    title: "Scheduled maintenance",
    body: "Planned downtime on Dec 22 from 02:00–04:00 UTC.",
    timestamp: "2 Days Ago",
    unread: false,
  },
  {
    id: 7,
    icon: (p: IconProps) => (
      <IconPlaceholder
        lucide="Check"
        tabler="IconCheck"
        hugeicons="Tick02Icon"
        phosphor="Check"
        remixicon="RiCheckLine"
        {...p}
      />
    ),
    title: "Deployment succeeded",
    body: "Production deploy v3.14.2 completed without errors.",
    timestamp: "3 Days Ago",
    unread: false,
  },
]

export default function NotificationsBlock() {
  const [notifications, setNotifications] = React.useState(initialNotifications)
  const unreadCount = notifications.filter((n) => n.unread).length

  const markAllRead = () =>
    setNotifications((prev) => prev.map((n) => ({ ...n, unread: false })))

  return (
    <section className="flex w-full items-center justify-center bg-background px-6 py-12 text-foreground">
      <Sheet defaultOpen>
        <SheetTrigger
          render={<Button variant="outline" size="icon" />}
          aria-label="Open notifications"
        >
          <IconPlaceholder
            lucide="Bell"
            tabler="IconBell"
            hugeicons="BellIcon"
            phosphor="Bell"
            remixicon="RiBellLine"
            className="size-4"
            aria-hidden="true"
          />
        </SheetTrigger>

        <SheetContent side="right" className="w-full p-0 sm:max-w-md">
          <SheetHeader className="flex-row items-center gap-2 space-y-0 pr-12">
            <SheetTitle className="flex items-center gap-2">
              Notifications
              {unreadCount > 0 && (
                <span className="flex size-5 items-center justify-center rounded-4xl bg-primary text-[10px] font-semibold text-primary-foreground">
                  {unreadCount}
                </span>
              )}
            </SheetTitle>
          </SheetHeader>

          <Separator />

          <ScrollArea className="flex-1 [&_[data-slot=scroll-area-viewport]]:scroll-fade-y">
            <ul className="flex flex-col">
              {notifications.map((item, index) => {
                const Icon = item.icon
                return (
                  <li key={item.id}>
                    <div
                      className={cn(
                        "flex items-start gap-3 px-4 py-3 transition-colors hover:bg-muted/50",
                        item.unread && "bg-muted/30"
                      )}
                    >
                      <span
                        className={cn(
                          "mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground",
                          item.unread && "bg-primary/10 text-primary"
                        )}
                      >
                        <Icon className="size-4" aria-hidden="true" />
                      </span>

                      <div className="flex min-w-0 flex-1 flex-col gap-0.5">
                        <div className="flex items-start justify-between gap-2">
                          <span
                            className={cn(
                              "truncate text-xs font-medium",
                              item.unread
                                ? "text-foreground"
                                : "text-muted-foreground"
                            )}
                          >
                            {item.title}
                          </span>
                          <span className="shrink-0 text-[10px] text-muted-foreground">
                            {item.timestamp}
                          </span>
                        </div>
                        <p className="text-xs/relaxed text-muted-foreground">
                          {item.body}
                        </p>
                      </div>

                      {item.unread && (
                        <span
                          className="mt-1.5 size-1.5 shrink-0 bg-primary"
                          role="img"
                          aria-label="Unread"
                        />
                      )}
                    </div>
                    {index < notifications.length - 1 && <Separator />}
                  </li>
                )
              })}
            </ul>
          </ScrollArea>

          <Separator />

          <div className="flex gap-2 p-3">
            <SheetClose
              render={<Button variant="outline" size="sm" className="flex-1" />}
            >
              Close
            </SheetClose>
            <Button
              size="sm"
              className="flex-1"
              onClick={markAllRead}
              disabled={unreadCount === 0}
            >
              <IconPlaceholder
                lucide="Check"
                tabler="IconCheck"
                hugeicons="Tick02Icon"
                phosphor="Check"
                remixicon="RiCheckLine"
                data-icon="inline-start"
              />
              Mark All Read
            </Button>
          </div>
        </SheetContent>
      </Sheet>
    </section>
  )
}
```
