DisplayAdded in Sierra

Dynamic Island

Adaptive notification and status hub that morphs between collapsed, expanded, and panel states.

Incoming Call

A ready-made call UI — dot, activity bar, and full panel with Decline/Answer, all driven by props. No local state, no custom markup.

Tap the island to cycle Dot → Bar → Panel

'use client'
import { DynamicIslandCall } from 'biibaos'
import { User } from 'lucide-react'

export default function App() {
  return (
    <DynamicIslandCall
      name="Abdul Wahab"
      subtitle="Incoming FaceTime · Video"
      avatarIcon={User}
      color="var(--biiba-success)"
      onAnswer={() => {/* accept the call */}}
      onDecline={() => {/* reject the call */}}
    />
  )
}

Now Playing / Music

Pass a track and playback state — the preset renders the collapsed icon, the activity-bar mini player, and the full panel with progress and controls.

Blinding Lights

The Weeknd

Tap the island to cycle Dot → Bar → Panel

'use client'
import { useState } from 'react'
import { DynamicIslandMusic } from 'biibaos'
import { Music2 } from 'lucide-react'

export default function App() {
  const [playing, setPlaying] = useState(true)

  return (
    <DynamicIslandMusic
      defaultState="expanded"
      track={{ title: 'Blinding Lights', artist: 'The Weeknd', icon: Music2, color: 'var(--biiba-warning)' }}
      playing={playing}
      progress={0.38}
      onTogglePlay={() => setPlaying(p => !p)}
      onNext={() => {/* skip to next track */}}
    />
  )
}

Timer / Stopwatch

A self-running countdown or count-up timer with real Pause/Resume and Restart controls, and a distinct completed state. Fires onComplete when a countdown reaches zero.

Tap the island, then Pause/Restart in the panel — this is a real, running kitchen timer

'use client'
import { useState } from 'react'
import { DynamicIslandTimer } from 'biibaos'

export default function App() {
  const [running, setRunning] = useState(true)
  const [key, setKey] = useState(0)

  return (
    <DynamicIslandTimer
      key={key}
      label="Pasta"
      initialSeconds={9 * 60}
      countDown
      running={running}
      onToggleRunning={() => setRunning(r => !r)}
      onRestart={() => { setRunning(true); setKey(k => k + 1) }}
      onComplete={() => {/* alert the user */}}
    />
  )
}

Order / Delivery Tracker

Step through delivery stages. The panel lists every stage with progress dots and a "Next stage" action.

Tap the island, then "Next stage" in the panel

'use client'
import { useState } from 'react'
import { DynamicIslandDelivery } from 'biibaos'

const stages = ['Order Placed', 'Preparing', 'Out for Delivery', 'Nearby', 'Delivered']

export default function App() {
  const [stage, setStage] = useState(2)

  return (
    <DynamicIslandDelivery
      stages={stages}
      stage={stage}
      color="var(--biiba-primary)"
      etaMinutesPerStage={7}
      onAdvance={setStage}
    />
  )
}

Notification Stack

Badge count on the collapsed dot, first notification on the activity bar, full grouped list in the panel.

3

Tap the island to cycle Dot → Bar → Panel

'use client'
import { DynamicIslandNotification } from 'biibaos'
import { MessageSquare, GitMerge, Triangle } from 'lucide-react'

const notifications = [
  { app: 'Slack', text: '#general: "Ship it!"', icon: MessageSquare },
  { app: 'GitHub', text: 'PR #84 merged into main', icon: GitMerge },
  { app: 'Vercel', text: 'Deploy succeeded', icon: Triangle },
]

export default function App() {
  return (
    <DynamicIslandNotification
      notifications={notifications}
      color="var(--biiba-danger)"
      onClear={() => {/* dismiss all */}}
    />
  )
}

Charging

Battery percentage with a live progress ring, a time-to-full estimate, and a real low-battery state — the accent switches to danger under 20% while unplugged, the same convention as the OS battery indicator.

Tap the island to cycle · click here to unplug (watch it go amber under 20%)

'use client'
import { useState, useEffect } from 'react'
import { DynamicIslandCharging } from 'biibaos'

export default function App() {
  const [percent, setPercent] = useState(62)
  const [charging, setCharging] = useState(true)

  useEffect(() => {
    const id = setInterval(
      () => setPercent(p => charging ? Math.min(100, p + 1) : Math.max(0, p - 1)),
      1200
    )
    return () => clearInterval(id)
  }, [charging])

  return (
    <DynamicIslandCharging
      percent={percent}
      charging={charging}
      minutesToFull={Math.round((100 - percent) * 1.4)}
      wattage="20W"
    />
  )
}

Voice Recording

A running mm:ss counter with a live waveform, plus Pause and Save so a take can be paused and kept — not just stopped and lost.

Tap the island, then Pause/Save/Stop in the panel — the waveform freezes on pause

'use client'
import { useState } from 'react'
import { DynamicIslandRecording } from 'biibaos'

export default function App() {
  const [paused, setPaused] = useState(false)

  return (
    <DynamicIslandRecording
      label="Voice Memo"
      paused={paused}
      onTogglePause={() => setPaused(p => !p)}
      onSave={() => {/* keep the take */}}
      onStop={() => {/* stop and discard */}}
    />
  )
}

Focus / Do Not Disturb

A focus-mode toggle. The panel shows current status and an on/off action.

Tap the island, then toggle it on/off in the panel

'use client'
import { DynamicIslandFocus } from 'biibaos'

export default function App() {
  return (
    <DynamicIslandFocus
      mode="Do Not Disturb"
      active
      color="var(--biiba-primary)"
      onToggle={(active) => {/* sync focus mode state */}}
    />
  )
}

Alarm

An alarm-ringing state with repeat days shown under the time, a snooze counter that persists across taps, and Snooze/Dismiss actions in the panel.

Tap the island, then Snooze a few times — the count actually persists

'use client'
import { useState } from 'react'
import { DynamicIslandAlarm } from 'biibaos'

export default function App() {
  const [snoozeCount, setSnoozeCount] = useState(0)

  return (
    <DynamicIslandAlarm
      label="Wake up"
      time="7:00 AM"
      repeatDays={['Mon', 'Tue', 'Wed', 'Thu', 'Fri']}
      snoozeCount={snoozeCount}
      onSnooze={() => setSnoozeCount(n => n + 1)}
      onDismiss={() => setSnoozeCount(0)}
    />
  )
}

Ride Share

Driver, car, and a live-counting ETA for a ride in progress, plus a trip progress bar, fare, and a Message action alongside Call — the kind of live activity a real rideshare app would push.

The ETA counts down and the trip bar fills in — like a real ride in progress

'use client'
import { useState, useEffect } from 'react'
import { DynamicIslandRide } from 'biibaos'

export default function App() {
  const totalMinutes = 8
  const [remaining, setRemaining] = useState(4)

  useEffect(() => {
    if (remaining <= 0) return
    const id = setInterval(() => setRemaining(m => Math.max(0, m - 1)), 3000)
    return () => clearInterval(id)
  }, [remaining])

  return (
    <DynamicIslandRide
      driverName="Kwame A."
      car="Toyota Corolla · Silver"
      plate="GR 4521-24"
      etaMinutes={remaining}
      rating={4.9}
      fare="₵45.00"
      tripProgress={1 - remaining / totalMinutes}
      onCall={() => {/* call driver */}}
      onMessage={() => {/* message driver */}}
      onCancel={() => {/* cancel ride */}}
    />
  )
}

Live Score

A compact score line on the activity bar, a full scoreboard with team badges and a pulsing LIVE indicator in the panel — the leading team's badge fills in solid, just like a real live-score push.

The score occasionally ticks up on its own, like a real live-game push

'use client'
import { useState, useEffect } from 'react'
import { DynamicIslandScore } from 'biibaos'

export default function App() {
  const [scores, setScores] = useState({ home: 2, away: 1 })

  useEffect(() => {
    const id = setInterval(() => {
      setScores(s => Math.random() > 0.5
        ? { ...s, home: s.home + 1 }
        : { ...s, away: s.away + 1 })
    }, 6000)
    return () => clearInterval(id)
  }, [])

  return (
    <DynamicIslandScore
      home="Accra City"
      away="Hearts"
      homeAbbr="ACC"
      awayAbbr="HRT"
      homeScore={scores.home}
      awayScore={scores.away}
      period="2nd Half · 71'"
      live
    />
  )
}

Uploading

A file upload with a live progress ring, and a status swap (uploading → success/error) that reflows the panel automatically. Fixed: the live preview here was staying stuck in 'uploading' forever even at 100% — because status is a prop you own, not something the component flips on its own — so it looked frozen with nothing happening. The preview now flips itself to 'success' once progress completes, and that's the pattern to copy in your own code below.

quarterly-report.pdf

Uploading · 15%

Tap the island to cycle · click here to bump progress

'use client'
import { useState, useEffect } from 'react'
import { DynamicIslandUpload } from 'biibaos'

export default function App() {
  const [progress, setProgress] = useState(0)
  const [status, setStatus] = useState<'uploading' | 'success' | 'error'>('uploading')

  useEffect(() => {
    if (progress >= 1) { setStatus('success'); return }
    const id = setTimeout(() => setProgress(p => Math.min(1, p + 0.1)), 400)
    return () => clearTimeout(id)
  }, [progress])

  return (
    <DynamicIslandUpload
      fileName="quarterly-report.pdf"
      progress={progress}
      status={status}
      color="var(--biiba-primary)"
      onCancel={() => {/* abort the upload */}}
      onRetry={() => setStatus('uploading')}
    />
  )
}

Downloading

A file download with speed label and Pause/Resume. It now also has a real completed state built in — once progress reaches 100% it swaps to a checkmark and 'Saved to Downloads' on its own, instead of just sitting at a full progress bar forever.

Downloads on its own — watch it flip to a checkmark at 100%

'use client'
import { useState, useEffect } from 'react'
import { DynamicIslandDownload } from 'biibaos'

export default function App() {
  const [progress, setProgress] = useState(0.64)
  const [paused, setPaused] = useState(false)

  useEffect(() => {
    if (paused || progress >= 1) return
    const id = setInterval(() => setProgress(p => Math.min(1, p + 0.05)), 500)
    return () => clearInterval(id)
  }, [paused, progress])

  return (
    <DynamicIslandDownload
      fileName="Biibaos-v4.dmg"
      progress={progress}
      speedLabel="4.2 MB/s"
      paused={paused}
      color="var(--biiba-success)"
      onTogglePause={() => setPaused(p => !p)}
      onCancel={() => {/* abort the download */}}
    />
  )
}

AI Processing

For long-running AI work — summarizing, transcribing, generating. Supports a determinate progress bar or an indeterminate shimmer when you don't have a percentage yet. Fixed: a longer stageText line was wrapping in the real panel but the hidden measurer sized the box for one line, so the wrapped part got clipped by the island's own overflow — this is why the panel could look empty. The measurer now wraps identically to the real render.

Generating summary

Reading transcript and cross-referencing prior notes · step 2 of 3

Tap the island to cycle Dot → Bar → Panel

'use client'
import { DynamicIslandAIProcessing } from 'biibaos'

export default function App() {
  return (
    <DynamicIslandAIProcessing
      label="Generating summary"
      stageText="Reading transcript · step 2 of 3"
      progress={0.55}
      color="var(--biiba-primary)"
      onCancel={() => {/* abort generation */}}
    />
  )
}

Custom — Build Your Own

Every demo above is a preset — this is the primitive underneath. DynamicIsland itself takes plain `collapsedContent` / `expandedContent` / `panelContent` and a `state` you own (it doesn't cycle itself), so you can build activities the presets don't cover. Here's a water-intake tracker built from scratch, entirely with theme tokens.

Built from the raw DynamicIsland primitive — its own useState, its own markup

'use client'
import { useState } from 'react'
import { DynamicIsland, Text } from 'biibaos'
import { Droplets, Plus } from 'lucide-react'

export default function App() {
  const [state, setState] = useState<'collapsed' | 'expanded' | 'panel'>('collapsed')
  const [cups, setCups] = useState(4)
  const goal = 8
  const cycle = () => setState(s => s === 'collapsed' ? 'expanded' : s === 'expanded' ? 'panel' : 'collapsed')

  return (
    <DynamicIsland
      state={state}
      onClick={cycle}
      glowColor="var(--biiba-primary)"
      collapsedContent={<Droplets size={18} color="var(--biiba-primary)" />}
      expandedContent={
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, width: '100%' }}>
          <Droplets size={20} color="var(--biiba-primary)" />
          <Text size={13} weight="semibold" style={{ color: 'var(--biiba-text)', flex: 1 }}>Water</Text>
          <Text size={13} weight="bold" style={{ color: 'var(--biiba-text)' }}>{cups}/{goal} cups</Text>
        </div>
      }
      panelContent={
        <div style={{ width: '100%', textAlign: 'center' }}>
          <Droplets size={32} color="var(--biiba-primary)" style={{ marginBottom: 8 }} />
          <Text size={22} weight="bold" style={{ color: 'var(--biiba-text)', display: 'block' }}>
            {cups} of {goal} cups
          </Text>
          <button
            onClick={(e) => { e.stopPropagation(); setCups(c => Math.min(goal, c + 1)) }}
            style={{
              padding: '8px 16px', borderRadius: 999, border: 'none',
              background: 'var(--biiba-primary)', color: 'var(--biiba-primary-fg)',
              fontWeight: 600, cursor: 'pointer', marginTop: 14,
            }}
          >
            <Plus size={14} /> Add a cup
          </button>
        </div>
      }
    />
  )
}

Properties

PropTypeDefaultDescription
state'collapsed' | 'expanded' | 'panel'Controlled state. Every preset manages this internally by default — pass `state` yourself only if you need to drive it externally.
defaultState'collapsed' | 'expanded' | 'panel''collapsed'Initial state for uncontrolled presets.
onStateChange(state) => voidFires whenever the island cycles, controlled or not.
cycleOnClickbooleantrueWhether tapping the island advances collapsed → expanded → panel. Set false to fully drive state yourself.
colorstringAccent color used for the icon badge, glow, and progress indicators across all three states.
size'sm' | 'md' | 'lg''md'Scales icon size, badge size, and type across the preset.
renderCollapsed / renderExpanded / renderPanel(ctx: { state, color }) => ReactNodeFull override for any one state's content — drop in your own markup without losing the island's shape animation.
position'top' | 'inline''top''top' fixes to the top of viewport; 'inline' renders in flow.
sizesPartial<Record<state, Partial<{ width, height, radius }>>>Override the exact width/height/radius for one or more states. Heights default to 'auto' for expanded/panel so the island always hugs its real content instead of a fixed box.
glow / glowPulsebooleantrue / falseAmbient color glow behind the glass, and whether it gently breathes at rest.