Timer
A working countdown, interval timer or stopwatch for the task at hand.
≈ 709 tokens per use (tool 431 · guidance 237 · typical payload 41)How token costs are estimated
Example: Timer
Set timers for the roast.
Lamb roast
When to use
- Timing matters to what the user is doing right now: cooking, steeping, resting dough, a workout, a pomodoro, a rehearsed talk.
- The user asks the agent to set a timer, run intervals, or time something.
When not to use
- The user needs a reminder at a clock time, hours or days away. Use a reminder or calendar tool.
- The time is only information, like "how long do eggs boil?". Answer in text.
- The agent would need to act when it ends without the user present. The timer runs in the user's browser.
How the agent should use it
- Give it a short title, the thing being timed: "Roast", "Tea", "Talk". It's used in every control label ("Pause Roast timer") and in the announcement at zero ("Roast is done").
- Use one step for a countdown, a sequence of labelled steps for intervals, and
repeatfor rounds instead of listing them. - Leave
autoStartoff unless the user asked to start now. The user presses Start. - On
completed, move on to the next thing they need. Oncancelled, don't restart it unasked.
How it works
| Mode | Shows | Controls |
|---|---|---|
countdown | Time left, a progress ring, "Ends at 4:12 PM" | Start / Pause / Resume, −1 min, +1 min, Reset, Cancel |
intervals | Time left in the step, the round, and the step list with the current step highlighted | The same, plus Skip step. ±1 min is off unless allowAdjust |
stopwatch | Elapsed time and the current lap | Start / Pause / Resume, Lap, Finish, Reset, Cancel |
- Time comes from the wall clock, not from counting ticks. The display refreshes four times a second and stops refreshing while the tab is hidden; a single timeout still catches the end, so the beep plays in a background tab.
- At zero the readout says "Done", screen readers hear "Roast is done" once, and a short beep plays if the user started the timer themselves (browsers only allow sound after a gesture). The page title is left alone. Restart runs it again; Dismiss leaves the finished timer in place.
- ±1 min changes the current step only. It can't take the last minute off: that's what Skip step is for.
- Laps are listed newest first. The fastest and slowest are labelled in text, not only by color. Split times are read to screen readers only.
- Reset and Skip step are icon buttons with full accessible names. Running / Paused isn't printed (the main button already says Pause or Resume); screen readers still hear it.
- Keyboard: Space starts and pauses when focus is on the timer (the readout is focusable). Every control names the timer: "Skip step in Roast timer".
Two kinds of history
A timer runs for minutes or hours, so there are two ways to restore it:
| Prop | Behavior | |
|---|---|---|
| Saved progress | onProgress → progress | Save the state on every start, pause, lap and adjustment. On reload the timer picks up where the wall clock says it is, still running if it was. Nothing is sent to the agent |
| A result | result | The run finished or was cancelled. It shows the finished timer, read-only |
<Timer
{...part.input}
progress={savedTimer}
onProgress={(state) => saveTimer(part.toolCallId, state)}
result={part.state === "output-available" ? part.output : undefined}
/>The saved state is small and holds no durations, only when it started and how much ran before:
{ startedAt: 1760000000000, elapsedBeforePause: 0, running: true, adjustSeconds: 0 }Installation
npx shadcn@latest add https://your-site.com/r/timer.json
Usage
import { tool } from "ai"
import { timerTool } from "@/components/genui/timer.schema"
const tools = {
[timerTool.name]: tool({
description: timerTool.description,
inputSchema: timerTool.inputSchema,
}),
}<Timer
{...part.input}
status={part.state === "input-streaming" ? "streaming" : undefined}
result={part.state === "output-available" ? part.output : undefined}
onResult={(result) =>
addToolResult({ tool: "start_timer", toolCallId: part.toolCallId, output: result })
}
/>The agent receives one result, when the run reaches zero, the user presses Finish on a stopwatch, or the user cancels:
{ action: "completed", totalSeconds: 6000 }
{ action: "completed", totalSeconds: 312.4, laps: [61.2, 94.8, 88, 68.4] } // stopwatch
{ action: "cancelled", elapsedSeconds: 95.3 }With LangGraph
Push it from a graph node with ui.push({ name: "start_timer", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
Pass the stored result back as result. A completed timer shows "Done" (a stopwatch shows its final time and laps), a cancelled one shows how long it ran, and onResult is never called again.
<Timer {...part.input} result={part.state === "output-available" ? part.output : undefined} />Schema
| Prop | Type | Notes |
|---|---|---|
title | string? | The thing being timed, up to 40 characters |
mode | "countdown" | "intervals" | "stopwatch" | |
steps | { label, seconds }[]? | Countdown: one step. Intervals: the sequence. Omit for a stopwatch |
repeat | number | Intervals: rounds of the sequence, 1–50. Default 1 |
autoStart | boolean | Default false: the user starts it |
allowAdjust | boolean? | Shows ±1 min. Default true for a countdown, false otherwise |
Host props: status (streaming, ready, completed, cancelled), onResult, onProgress, progress, result and className.