# Timer

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

> For the full documentation index, see [llms.txt](https://islandcn.dev/llms.txt). Every page has a Markdown version: add `.md` to its URL or send `Accept: text/markdown`. To build an island, follow the islandcn agent skill at https://islandcn.dev/.well-known/agent-skills/islandcn/SKILL.md.

![The Timer preset, expanded, at real size.](/docs/presets/timer.png)

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

```package-install
npx 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](/docs/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

```tsx components/tea-timer.tsx
"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

| Prop | Type | Default | Description |
| - | - | - | - |
| `state` | `{ kind: "running"; endsAt: number } \| { kind: "paused"; remaining: number }` | - | Running with an end time (epoch ms), or paused with the time left (ms). |
| `duration` | `number` | - | Full length in ms, for the ring in the minimal fallback. |
| `label?` | `string` | `"Timer"` | Name in the expanded view. |
| `accent?` | `string` | `orange` | Accent colour. |
| `onToggle?` | `() => void` | - | Pause or resume button in the expanded view. |
| `onCancel?` | `() => void` | - | Cancel button in the expanded view. |
| `onEnd?` | `() => void` | - | Called when a running timer reaches zero. |
| `id?` | `string` | `"timer"` | Activity id. |
| `priority?` | `number` | `0` | Higher priority takes the main pill when two activities run. |

## Make it yours

Install this preset when it is exactly what you need. Otherwise open it in the [Playground](https://islandcn.dev/playground), change what each region shows and copy the code, or read its source for technique and build your own island on the engine.
