Early bird: 30% off Dashral ProSee pricing

Dashral

Status IslandPro

One pill that shows what's going on: small while something runs, wider for a moment when something new happens (with Undo or Reply), open into the whole list on a click.

Installation

When to use

  • Things that run in the background and finish later: uploads, exports, syncs, with progress while small.
  • A quick way back from what just happened: a deleted row with Undo, a message with Reply, without a toast stack.

When not to use

  • Something the person must answer before going on: a dialog asks it.
  • Long messages or many at once: a notifications page or inbox holds them better.

Usage

Status Island with your own data: a whole component you can paste as a page.

example.tsx
"use client"

import { StatusIsland } from "@/components/dashral/pro/status-island"

import { useState } from "react"
import { Trash2, Upload } from "lucide-react"

export default function App() {
  const [deleted, setDeleted] = useState(true)
  const items = [
    { id: "upload", title: "Uploading 3 files", icon: <Upload />, progress: 0.4 },
    ...(deleted
      ? [{ id: "deleted", title: "Invoice deleted", detail: "#1042 · Halcyon Studio", icon: <Trash2 />, actions: [{ label: "Undo", onClick: () => setDeleted(false) }] }]
      : []),
  ]
  return <StatusIsland items={items} idle="All synced" />
}

API reference

StatusIsland

A status island: one pill that shows what's going on. Small while something runs, wider for a moment when something new happens (with its first action), open into the whole list on a click; Escape or a click outside closes it.

PropTypeDefault
itemsIslandItem[]

Required. What's going on, oldest first: the newest is the one shown while small.

—
idleReact.ReactNode

Shown while nothing's going on (a clock, a brand); without it the island hides.

—
peeknumber

How long a new item (or one turning success) stays wide, in ms.

3200
widthnumber

Its width while open, in px (never wider than its container).

360
labelstring

Named for screen readers; "Activity" by default.

"Activity"
classNamestring—
classNamesStatusIslandClassNames—

type IslandAction

PropTypeDefault
labelstring—
onClick() => void—
primaryboolean

The one that stands out (filled).

—

type IslandItem

PropTypeDefault
idstring—
titlestring—
detailstring—
iconReact.ReactNode—
progressnumber

0 to 1: a ring while small, a bar while open.

—
tone"default" | "success" | "danger"

success shows a check (and peeks once more); danger tints its icon.

—
actionsIslandAction[]—
onDismiss() => void

Adds × to its row while open.

—

type StatusIslandClassNames

PropTypeDefault
rootstring—
islandstring—

Keyboard

KeysAction
TabReach the island
EnterorSpaceOpen it into the whole list
↑or↓Along the items while open
TabTo an item's actions and ×
EscClose; the focus returns to the island

Accessibility

  • A group with the role description "status island", named with its newest item ("Activity: Uploading 3 files. Press Enter to open"); open, it is a dialog.
  • A live region reads the newest item's words as they change, so progress and results are heard without opening it.
  • An action is a real button inside the island; × on an item is named "Dismiss <title>".

Motion

  • One surface: its width and height move from the small pill to the wider peek and the open list on a long, barely bouncing spring (0.8 s, bounce 0.2), so a change mid-move carries on smoothly instead of starting again; its corner stays the same and the resize alone turns the pill into a panel.
  • Its content unfurls down out of the pill line (from 90%, 8px up, a 5px blur) with the shell, and leaves in 0.08 s, before the shrinking shell can clip it; something new while it's wide gathers it back into the small pill first, then widens it again. Progress fills a ring while small and a bar while open, then turns to a check.
  • The island's spring is the one spring in the Pro components, chosen because events reshape it mid-move; everything else in it is a smooth tween. With reduced motion it only fades.