Dynamic Island

A floating, expandable notification component inspired by Apple's Dynamic Island. Features notification stacking with navigation dots, dismissable vs persistent notifications, priority-based ordering, and auto-collapse.

Preview

Click the button below to trigger a notification.
The Dynamic Island will appear at the top of the viewport.

Installation

Use the CLI to add this component to your project.

npx shadcn@latest add https://ui.nyxhora.com/r/dynamic-island.json

Setup

Wrap your app with DynamicIslandProvider.

Layout Setup

tsx
24 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// app/layout.tsx
import { DynamicIslandProvider } from "@/components/ui/dynamic-island-provider"

export const metadata = generateComponentMetadata({
    name: "Dynamic Island",
    description: "A floating, expandable notification component inspired by Apple's Dynamic Island. Features notification stacking with navigation dots, dismissable vs persistent notifications, priority-based ordering, and auto-collapse.",
    category: "Overlay",
});


export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <DynamicIslandProvider 
          defaultPosition="top-center"
          autoCollapseTimeout={10000} // 10s
        >
          {children}
        </DynamicIslandProvider>
      </body>
    </html>
  )
}

Usage

tsx
20 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { useDynamicIsland, DynamicIslandAction } from "@/components/ui/dynamic-island-provider"

function MyComponent() {
  const { push, dismiss, collapse } = useDynamicIsland();

  const notify = () => {
    push({
      icon: <Bell className="h-4 w-4" />,
      title: "New Message",
      subtitle: "From Sarah",
      variant: "default",
      duration: 5000,     // auto-dismiss in 5s (0 = persistent)
      dismissable: true,  // can be dismissed (default)
      priority: 0,        // higher = shows first
      content: <CustomContent />,
    });
  };

  return <Button onClick={notify}>Notify</Button>
}

Positions

Current position: top-center

Variants

Notification Stacking

Multiple notifications queue up. Expand to see navigation dots at the bottom for switching between notifications. Recent/high-priority notifications appear first.

Queue: 0 notifications

Push multiple notifications, then expand to see navigation dots

Dismissable vs Persistent

Some notifications (like calls or music players) should not be dismissable. Use dismissable: false and priority to control behavior.

Non-dismissable notifications (calls, music) cannot be swiped away.
Use dismissable: false and priority: 10

Auto-dismiss

Set duration for automatic dismissal. Expanded view auto-collapses after 10s of inactivity.

Expanded view auto-collapses after 10s of inactivity

Examples

Music Player (Non-Dismissable)

Music player is non-dismissable

Incoming Call (Highest Priority)

Calls have highest priority and cannot be dismissed

Hook API

MethodDescription
push(notification)Add notification to queue, returns id
dismiss(id)Dismiss (only if dismissable)
dismissAll()Clear all dismissable notifications
collapse()Collapse expanded view
setPosition(pos)Set global position
setActiveIndex(i)Switch to notification at index

Props

PropTypeDefaultDescription
titlestringrequiredTitle text for the notification
iconReactNodeundefinedIcon to display
subtitlestringundefinedSecondary text below title
contentReactNodeundefinedCustom expanded content
variant'default' | 'success' | 'error' | 'warning'defaultVisual variant with shadow color
durationnumber5000Auto-dismiss in ms. 0 = persistent.
dismissablebooleantrueIf false, cannot be dismissed (e.g., calls)
prioritynumber0Higher priority shows first

Features

Navigation Dots

Switch between stacked notifications using dots when expanded.

Auto-Collapse

Expanded view collapses after 10s of inactivity.

Priority Ordering

High priority notifications (calls) always show first.

Persistent Notifications

Calls and music players can't be dismissed, only collapsed.