Tech Store

Showing suggestions.

Opening the store's index…
↑↓ to move↵ to openesc to close
Tech Store search
Componentv1.0.0~9.5 KB gzipped

Dynamic Island

Notifications that spring out of one black pill and melt back into it

FreeFree
Format

.zip · HTML / CSS / JS · v1.0.0

Free — no account, no email. Always the newest version.

Live preview

Version
Preview width
Zoom

About

Your app's notifications, told by one small black pill. It sits at the top of the screen and, when there is something to say, springs open into exactly the size of the message — then melts back. A save becomes a tick that draws itself; a payment starts as a spinner and morphs into the receipt; an upload fills a bar in place; a timer ticks quietly in the compact island and opens into its controls when you hover or tap it.

  • Toast, loading → result, progress, buttons and live activities, all from one show() call
  • The morph runs on a real spring, solved once and handed to CSS as a linear() easing — smooth at any frame rate, with nothing running in JavaScript while it moves
  • A queue that behaves: one island, toasts take turns, a live activity steps aside for a notification and comes back after it
  • Every colour, size and the spring itself are CSS custom properties; light and dark follow your page
  • Nothing to install: one CSS file and one small script, or one React component

Features · 11

  • One island that morphs — width, height, corners and content — on a real spring
  • Toasts in four tones, each with an icon that draws or pops in
  • promise(): a spinner that morphs into the result, success or error
  • Progress as a ring around the icon or a bar, driven by the handle show() returns
  • Buttons on the island, such as Undo, with a callback each
  • Live activities (a timer, the song playing) that expand on hover, focus or tap
  • A queue: toasts take turns, live activities step aside and come back
  • Pauses while hovered, focused or in a background tab; swipe or Esc to dismiss
  • Top or bottom of the screen; no-code data-island buttons
  • Plain HTML/CSS/JS and React (Next.js ready) builds
  • Zero dependencies, under 10 KB gzipped (script and styles together)

How to use it

Link the stylesheet and the script once. The island builds itself the first time it has something to say:

HTML
<link rel="stylesheet" href="dynamic-island.css" />
<script src="dynamic-island.js" defer></script>

<script>
  document.addEventListener("DOMContentLoaded", () => {
    DynamicIsland.show({ title: "Saved", description: "All changes are in", tone: "success" });
  });
</script>

No code at all: any element with data-island shows its message when clicked.

HTML
<button type="button" data-island data-island-title="Link copied" data-island-tone="success"
        data-island-action="Undo" data-island-action-title="Copy undone">
  Copy link
</button>

Attributes: data-island-title (the element's text when missing), -description, -tone, -icon (text, such as an emoji), -trailing, -duration (ms, 0 stays until dismissed), -id, -action (a button's label) and -action-title (shown once it is pressed). Triggers added later work too; DynamicIsland.init(container) listens inside another root.

Every mode

JavaScript
// A toast: neutral, success, error, info or warning
DynamicIsland.show({ title: "Card declined", description: "Try another card", tone: "error" });

// Loading → result
DynamicIsland.promise(pay(), {
  loading: "Paying…",
  success: (receipt) => ({ title: "Paid", description: receipt.total }),
  error: (error) => ({ title: "Payment failed", description: error.message }),
});

// Progress: a ring around the icon, or a bar
const upload = DynamicIsland.show({ title: "Uploading", progress: 0, progressStyle: "bar", trailing: "0%" });
upload.update({ progress: 0.6, trailing: "60%" });
upload.update({ title: "Uploaded", tone: "success", progress: null, trailing: undefined });

// Buttons
DynamicIsland.show({
  title: "Message deleted",
  actions: [{ label: "Undo", variant: "primary", onClick: () => restore() }],
});

// A live activity: compact until hovered, focused or tapped
const timer = DynamicIsland.show({
  title: "Timer", trailing: "4:59", description: "Pasta", expandable: true, accent: "#ff9f0a",
  actions: [{ label: "Pause", dismiss: false, onClick: (handle) => handle.update({ description: "Paused" }) }],
});

show() returns { id, update(changes), dismiss() }. Also: DynamicIsland.update(id, changes), dismiss(id?) (the one on screen without an id), clear() and configure(options).

Messages

Field Default What it does
title — The headline (required)
description — A second line; in a live activity, shown when expanded
tone "neutral" success, error, info, warning: icon and colour
icon the tone's Text (an emoji) or a DOM node, cloned; any React node in React
loading false A spinner instead of the icon
progress null 0 to 1; progressStyle: "ring" (default) or "bar"
trailing — Short text at the far end: "4:59", "42%"
actions — { label, onClick(handle), variant: "primary", dismiss: false }
expandable false A live activity: compact until hovered, focused or tapped
accent the tone's A colour for the icon, progress and primary button
duration 4 s, 6 s for errors and buttons ms; Infinity or 0 stays. Loading, progress and live activities stay by default
dismissible true Whether swipe and Esc dismiss it
id generated Showing the same id again updates that message
onDismiss(reason) — "timeout", "swipe", "escape", "action" or "api"

There is one island, so messages take turns: toasts show one after another and are never cut short; a message that stays (a live activity, an upload) steps aside for a toast and comes back after it. The countdown pauses while the island is hovered or focused and while the tab is in the background. Swipe it toward its edge, or press Esc while it is hovered or focused, to dismiss.

Options

DynamicIsland.configure({ … }), at any time:

Option Default What it does
position "top" "top" or "bottom" of the screen
label "Notifications" The landmark's accessible name
container document.body Where the island is added; inside any other element (give it position: relative) it sits in that element, like a phone mockup
spring the stylesheet's { stiffness, damping, mass } for the morph, turned into a linear() easing

Ask about this product

Not sure it fits your project? Ask — the answer comes from this page.

Answers are written by AI from this page and can be wrong. The page itself is what counts.

Changelog

  1. 1.0.0First releaseLatest

    Plain HTML/CSS/JS and React builds of a spring-morphing island: toasts, promise and progress states, actions, expandable live activities, a smart queue, swipe and Esc to dismiss, and full screen-reader and reduced-motion support.

  2. Fixes and new features land behind the same download, which always serves the latest version.