Skip to content
txbrendan/ui

Notifications

beta

A bell with the count of what's new, and the list it opens, grouped by day, with Mark all as read.

Preview

new

all read

inline

Notifications

Today

Yesterday

Earlier

empty

Notifications

You're all caught up.

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/notifications

Use it for

  • The bell in an app's header: what happened elsewhere while you were away, like an approval or a meeting moved.
  • Give each item a title for what happened, the person who did it (or an icon), and meta for when and where: "18:04 · Design".
  • Group items with group: "Today", "Yesterday", "Earlier". Groups show in the order they first appear.
  • Opening one marks it read, closes the list, and calls onOpenItem. Take people to what it is about there.
  • Mark all as read clears the dots at once, with Undo for 8 seconds. ⌘Z takes it back too. onReadAll runs when the window ends.
  • Use inline to draw the open list in a card or a doc, with no bell.

Avoid it for

  • News with a place on the screen. Show it there, like a status on its row.
  • The result of something you just did. Show it on the control you pressed.
  • Messages people reply to. Use a messages list.

Props

  • items

    Required. { id, title, person?, photo?, icon?: ReactNode, meta?, group?, read? }.

    NotificationItem[]

  • onOpenItem

    Opens what it is about.

    (item: NotificationItem) => void

  • onRead

    Marked read by opening it.

    (ids: string[]) => void

  • onReadAll

    Mark all as read, once its 8-second undo window ends.

    (ids: string[]) => void

  • inline

    Draws the open list in the page's flow, with no bell.

    boolean · default false

  • className

    Extra classes on the bell, or on the list with inline. Later classes win.

    string

Accessibility

  • The bell is a button named with the count, like "Notifications, 3 new". It says it opens a dialog.
  • The list is a dialog named "Notifications". Tab moves through Mark all as read, Close, and the rows, and wraps inside the list, even when pressed fast. The rows carry tabindex="0", so Safari keeps them in Tab too. Esc or Close shuts it and puts focus back on the bell.
  • Each row is a button. A new one ends with a dot, which screen readers hear as "New", and forced-colors mode draws as a ring.
  • Mark all as read says how many it marked in a polite live region. Its Undo undoes only once 1.2 seconds have passed, so a double press never undoes.

Examples

In a header

import { CalendarDays } from "lucide-react";

<Notifications
  items={[
    { id: "1", person: "Kate Clarke", title: "Kate Clarke agreed to the launch plan", meta: "18:04 · Design", group: "Today" },
    { id: "2", icon: <CalendarDays />, title: "Saturday's workshop moved to 11:00", meta: "Calendar", group: "Yesterday", read: true },
  ]}
  onOpenItem={(item) => console.log("open", item.id)}
/>