Skip to content
islandcn
Esc
↑↓navigate↵open⌘Jpreview
On this page

Timer

A countdown with the time left on the trailing side and pause and cancel buttons when expanded.

The Timer preset, expanded, at real size.

A countdown that ticks on its own. You pass when it ends (or how much is left while paused); the time in the island updates every second without re-rendering your component.

Install

npx shadcn@latest add https://islandcn.dev/r/timer-activity.json
pnpm dlx shadcn@latest add https://islandcn.dev/r/timer-activity.json
yarn dlx shadcn@latest add https://islandcn.dev/r/timer-activity.json
bunx shadcn@latest add https://islandcn.dev/r/timer-activity.json

This writes components/islandcn/timer-activity.tsx, plus the content kit and the engine if the project lacks them. Mount the island once first, see Installation.

What it shows

  • Compact: a timer glyph and the time left.
  • Split: its compact pair, as the reference phone shows it, in the pill or in the 62.18 wide capsule. When the pair is too wide for the split, the fallback is a ring that empties as time runs out, with the time left beside it.
  • Expanded: pause and cancel buttons when you pass handlers, the label, and the time left in large type.
  • Updates: the time turns over its digits each second with the engine’s IslandNumber. In the expanded view the time sits in a frame as tall as the buttons, so the two stay level on the top-aligned row.

Usage

"use client";

import { useState } from "react";

import { TimerActivity, type TimerState } from "@/components/islandcn/timer-activity";

export const TeaTimer = () => {
  const [state, setState] = useState<TimerState | null>(() => ({ kind: "running", endsAt: Date.now() + 4 * 60_000 }));
  if (!state) return null;
  return (
    <TimerActivity
      label="Tea"
      state={state}
      duration={4 * 60_000}
      onToggle={() =>
        setState((s) =>
          s?.kind === "running" ? { kind: "paused", remaining: s.endsAt - Date.now() } : s && { kind: "running", endsAt: Date.now() + s.remaining },
        )
      }
      onCancel={() => setState(null)}
      onEnd={() => setState(null)}
    />
  );
};

Props

PropType
state{ kind: "running"; endsAt: number } | { kind: "paused"; remaining: number }

Running with an end time (epoch ms), or paused with the time left (ms).

Type{ kind: "running"; endsAt: number } | { kind: "paused"; remaining: number }
durationnumber

Full length in ms, for the ring in the minimal fallback.

Typenumber
label?string

Name in the expanded view.

Typestring
Default"Timer"
accent?string

Accent colour.

Typestring
Defaultorange
onToggle?() => void

Pause or resume button in the expanded view.

Type() => void
onCancel?() => void

Cancel button in the expanded view.

Type() => void
onEnd?() => void

Called when a running timer reaches zero.

Type() => void
id?string

Activity id.

Typestring
Default"timer"
priority?number

Higher priority takes the main pill when two activities run.

Typenumber
Default0

Make it yours

Install this preset when it is exactly what you need. Otherwise open it in the Playground, change what each region shows and copy the code, or read its source for technique and build your own island on the engine.

Was this page helpful?