Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Notification List Skeleton Block for React

A design-review inbox that loads in place: six notifications with unread dots, avatars and times, and a Mark all read button that clears the dots and the unread count.

Framework
UI library

Preview

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Overview

Show a notification list while it loads, then swap the notifications in without moving anything, with this notification list skeleton for React with Base UI. A hairline frame with a plus mark on each corner holds a band heading with the unread count, a Mark all read button and a flat list of six notifications from Glissando, a design-review notifier. The heading and the button are real from the first paint; each dot, avatar, sentence, line of context and time is a Skeleton bar that becomes the data in the same cell, row by row, when you press Load. Mark all read is real and local: it clears the three unread dots and the count, and Reload brings them back. The control is an outline Button, the bands are ruled by Guides, and a polite line says what loaded. It's built from Skeleton, Avatar, Button and Guides, with a CSS entrance only. For inboxes, review queues and any list where some rows are new.

FAQ

What stays local, and how do I wire it to a real query?

Nothing is fetched. The six notifications are typed in NOTES, three of them unread, Load starts a 700 millisecond timer, and when it ends the phase becomes ready and the data swaps in; Reload goes back to the bars and in again. To wire it for real, delete the timer, the Load button and the polite line, and set data-state (loading or ready) and aria-busy on the region from your query's pending state, or use the bars as a Suspense fallback and render the real rows when they arrive. Mark all read would send one request and refresh the list from the response. The bars are derived from the same array as the data, so keep one row component for both.

Why doesn't the page jump when the data arrives?

Each bar and its data sit in one grid cell, so the larger of the two sets the cell's box and the swap changes nothing around it. The unread dot keeps its place when it clears (it turns transparent instead of leaving), and the font weight that marks an unread sentence changes inside a truncated line, so no row changes height. The polite line under the panel reserves two lines, so the panel is exactly as tall before, during and after loading at every width.

What does Mark all read do?

It clears the unread dots, the bold weight of the sentences and the count in the heading, then says so in the polite line. While the bars are showing, or when nothing is unread, the button is marked aria-disabled and ignores presses, so it keeps keyboard focus instead of dropping it. Reload restores the three unread notifications.

How do screen reader users know which are unread?

Each unread sentence starts with a screen-reader-only "Unread.", so the state doesn't depend on the dot or the weight. The dot is decoration, hidden from assistive technology, and it uses the foreground colour rather than a status colour so it reads in any theme.

Is it accessible?

The region is marked busy while it loads and carries a screen-reader-only "Loading notifications…", and the polite line announces what loaded once. The bars are hidden from assistive technology and the layer that isn't showing is invisible, so it is neither read nor focusable. The pulse stops when the data is in, and under reduced motion there is no pulse, no spinner and no stagger: the swap is instant.