<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# Timer

A countdown timer with its own controls: the time left in large numbers, Start, then Pause and Cancel, Resume when paused, and at zero "Time's up" with Dismiss and Restart. It keeps time by the clock, so it stays right in a background tab and, with `timerId`, across a reload. At zero it chimes and vibrates (unless `silent`), again every 2.5 seconds until stopped with `alert="repeat"`, and screen readers hear "Pasta: Time's up" once; the time itself is a `timer` region, read when the person asks rather than every second.

```tsx
import { Timer } from "@/components/ouro/timer/timer";
```

## Example

```tsx
<Timer label="Pasta" duration={600} timerId="pasta" />
<Timer label="Roast" duration={5400} timerId="roast" alert="repeat" />
```

## Usage

Give timers that should outlive their screen a `timerId` and list them in a `TimerTray`, so a person who moves on can still see and stop them.

## Use instead when

- Use `Progress` instead for how far a task has got, and `useTimer` for a timer of your own design.

## Props

Type: `TimerProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `alert` | `"once" \| "repeat"` | `"once"` | How it sounds when the time is up. `once` chimes and vibrates once. `repeat` chimes and vibrates again every 2.5 seconds, for a timer that may finish while the person is across the room, until they dismiss, restart or cancel it (here, in a `TimerTray` or in any view with its `timerId`) or `repeatFor` seconds pass. Either way screen readers hear "Time's up" once, and `silent` keeps it quiet. |
| `className` | `string` |  | Layout only: margins, width (`mt-6`). Appearance classes warn in development. |
| `duration` (required) | `number` |  | How long it runs, in seconds: `duration={600}`. |
| `label` (required) | `string` |  | What the timer is for, shown above the time and naming it for screen readers: "Pasta", "Break". |
| `labels` | `TimerLabels` |  | The words it shows and says, for apps in other languages. |
| `onDone` | `((event: TimerDoneEvent) => void)` |  | Called once when the timer reaches zero, including when it ended while the page was closed. |
| `ref` | `Ref<HTMLDivElement>` |  | Reaches the rendered element. |
| `repeatFor` | `number` | `120` | With `alert="repeat"`, how long it keeps sounding, in seconds from when the time was up, unless stopped. A value that is not finite and above 0 is taken as 120, and development warns. The alert also stops once no view of the timer (a `Timer` or a `TimerTray` row with its `timerId`) is left on the page. |
| `silent` | `boolean` |  | No chime and no vibration at zero; it still shows and says that the time is up. |
| `size` | `"md" \| "lg"` | `"md"` | How large the time is: `md` beside other content, `lg` as a screen's focus. |
| `timerId` | `string` |  | Keeps the timer through a reload and a closed tab, and shares it with a `TimerTray` and every other `Timer` or `useTimer` given the same `timerId`. Without it the timer ends with the component. (`id` is the element's, as on any `<div>`.) |

## Native attributes

Also accepts the attributes in React's `HTMLAttributes` (`id`, `aria-*`, event handlers), passed to the rendered element, except those named on this page.

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `autoSaveId`: Not a prop. A timer is kept and shared by `timerId`: `<Timer timerId="pasta" />`.
- `minutes`: Not a prop. The length is `duration`, in seconds: `<Timer duration={10 * 60} />`.
- `onComplete`: Not a prop. Ouro UI says `onDone`: `<Timer onDone={() => …} />`.
- `onEnd`: Not a prop. Ouro UI says `onDone`: `<Timer onDone={() => …} />`.
- `seconds`: Not a prop. The length is `duration`, in seconds: `<Timer duration={600} />`.
- `sound`: Not a prop. Turn the sound off with `silent`: `<Timer silent />`.
- `storageKey`: Not a prop. A timer is kept and shared by `timerId`: `<Timer timerId="pasta" />`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`). Your classes always win over Ouro UI's: Timer merges `className` last with `cn` (tailwind-merge), so a class that conflicts with its own replaces it.[^layout-classes]

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `TimerDoneEvent`

What a timer's `onDone` hears when it reaches zero.

```ts
interface TimerDoneEvent {
  /**
   * When it reached zero, in milliseconds since the epoch: in the past when the page was closed at the time.
   */
  endedAt: number;
  /**
   * Whether this `useTimer` is the one on the page that should sound the alert: the first with
   * the timer's `timerId` to hear it, not counting `fallback` ones unless only those are mounted. When
   * a `Timer` and a `TimerTray` both show one timer, the `Timer` alerts, whichever mounted first.
   * A hook without `onDone` reads the timer without taking the alert.
   */
  first: boolean;
}
```

### `TimerLabels`

The words a timer shows and says, for apps in other languages.

```ts
interface TimerLabels {
  /** @default "Start" */
  start?: string | undefined;
  /** @default "Pause" */
  pause?: string | undefined;
  /** @default "Resume" */
  resume?: string | undefined;
  /** @default "Cancel" */
  cancel?: string | undefined;
  /** @default "Dismiss" */
  dismiss?: string | undefined;
  /** @default "Restart" */
  restart?: string | undefined;
  /**
   * What it shows, and screen readers hear, at zero. @default "Time's up"
   */
  done?: string | undefined;
  /** What it shows after the time while paused. @default "Paused" */
  paused?: string | undefined;
  /** TimerTray's link to the timer's page. @default "Go to" */
  goTo?: string | undefined;
}
```

[^layout-classes]: test:registry/ouro/components/ouro/timer/timer.test.tsx::Timer > forwards a ref, passes attributes through, and lets layout classes win verify:testExists(registry/ouro/components/ouro/timer/timer.test.tsx::Timer > forwards a ref, passes attributes through, and lets layout classes win)
