Shadcn Timer for React and Tailwind
Countdown or stopwatch with controls.
Installation
bunx --bun shadcn@latest add https://kit.dev/r/timer.jsonpnpm dlx shadcn@latest add https://kit.dev/r/timer.jsonnpx shadcn@latest add https://kit.dev/r/timer.jsonyarn shadcn@latest add https://kit.dev/r/timer.jsonInstall the following dependencies:
bun add @ark-ui/reactpnpm add @ark-ui/reactnpm install @ark-ui/reactyarn add @ark-ui/reactCopy and paste the following code into your project.
"use client";
import { ark } from "@ark-ui/react/factory";
import {
Timer as ArkTimer,
useTimerContext as useArkTimer,
} from "@ark-ui/react/timer";
import type React from "react";
import { cn } from "@/lib/utils";
export const useTimer = useArkTimer;
export const remainingMsUntilDate = (date: Date): number => {
const end = new Date(date).getTime();
return Math.max(0, end - Date.now());
};
export const Timer = (props: React.ComponentProps<typeof ArkTimer.Root>) => {
const { className, ...rest } = props;
return (
<ArkTimer.Root
className={cn(
"min-w-0",
"flex flex-col items-start gap-4",
"text-foreground",
className
)}
data-slot="timer"
{...rest}
/>
);
};
export const TimerArea = (
props: React.ComponentProps<typeof ArkTimer.Area>
) => {
const { className, ...rest } = props;
return (
<ArkTimer.Area
className={cn(
"flex items-center gap-2",
"has-data-[slot=timer-item-label]:items-start",
className
)}
data-slot="timer-area"
{...rest}
/>
);
};
interface TimerItemGroupProps extends React.ComponentProps<typeof ark.div> {
/**
* The orientation of the timer item group.
*
* @default "vertical"
*/
orientation?: "horizontal" | "vertical";
}
export const TimerItemGroup = (props: TimerItemGroupProps) => {
const { orientation = "vertical", className, ...rest } = props;
return (
<ark.div
className={cn(
"flex items-center",
"data-[orientation=horizontal]:flex-row",
"data-[orientation=vertical]:flex-col",
className
)}
data-orientation={orientation}
data-slot="timer-item-group"
{...rest}
/>
);
};
export const TimerItem = (
props: React.ComponentProps<typeof ArkTimer.Item>
) => {
const { className, ...rest } = props;
return (
<ArkTimer.Item
className={cn(
"w-fit min-w-[2.5ch]",
"text-center font-semibold text-3xl text-foreground tabular-nums tracking-wider",
className
)}
data-slot="timer-item"
{...rest}
/>
);
};
export const TimerItemLabel = (props: React.ComponentProps<typeof ark.div>) => {
const { className, ...rest } = props;
return (
<ark.div
className={cn("text-muted-foreground text-xs", className)}
data-slot="timer-item-label"
{...rest}
/>
);
};
export const TimerSeparator = (
props: React.ComponentProps<typeof ArkTimer.Separator>
) => {
const { className, children, ...rest } = props;
return (
<ArkTimer.Separator
className={cn("font-semibold text-2xl text-muted-foreground", className)}
data-slot="timer-separator"
{...rest}
>
{children ?? ":"}
</ArkTimer.Separator>
);
};
export const TimerControl = (
props: React.ComponentProps<typeof ArkTimer.Control>
) => {
const { className, ...rest } = props;
return (
<ArkTimer.Control
className={cn("flex items-center gap-2", className)}
data-slot="timer-control"
{...rest}
/>
);
};
export const TimerActionTrigger = (
props: React.ComponentProps<typeof ArkTimer.ActionTrigger>
) => <ArkTimer.ActionTrigger data-slot="timer-action" {...props} />;
interface TimerActionProps
extends Omit<React.ComponentProps<typeof ArkTimer.ActionTrigger>, "action"> {}
export const TimerPause = (props: TimerActionProps) => (
<ArkTimer.ActionTrigger
aria-label="Pause"
data-slot="timer-pause"
{...props}
action="pause"
/>
);
export const TimerResume = (props: TimerActionProps) => (
<ArkTimer.ActionTrigger
aria-label="Resume"
data-slot="timer-resume"
{...props}
action="resume"
/>
);
export const TimerStart = (props: TimerActionProps) => (
<ArkTimer.ActionTrigger
aria-label="Start"
data-slot="timer-start"
{...props}
action="start"
/>
);
export const TimerReset = (props: TimerActionProps) => (
<ArkTimer.ActionTrigger
aria-label="Reset"
data-slot="timer-reset"
{...props}
action="reset"
/>
);
export const TimerRestart = (props: TimerActionProps) => (
<ArkTimer.ActionTrigger
aria-label="Restart"
data-slot="timer-restart"
{...props}
action="restart"
/>
);
export const TimerPlay = (props: TimerActionProps) => {
const { paused } = useArkTimer();
if (paused) {
return <TimerResume {...props} />;
}
return <TimerStart {...props} />;
};Update the import paths to match your project setup.
Anatomy
Timer
├── TimerArea
│ ├── TimerItemGroup
│ │ ├── TimerItem
│ │ └── TimerItemLabel
│ ├── TimerSeparator
└── TimerControl
├── TimerActionTrigger
├── TimerStart
├── TimerPause
├── TimerResume
├── TimerReset
├── TimerRestart
└── TimerPlayUsage
import {
Timer,
TimerArea,
TimerItem,
TimerItemGroup,
TimerItemLabel,
TimerSeparator,
TimerControl,
TimerActionTrigger,
TimerStart,
TimerPause,
TimerResume,
TimerReset,
TimerRestart,
TimerPlay,
} from "@/components/ui/timer";<Timer targetMs={3600000} startMs={2400000}>
<TimerArea>
<TimerItemGroup>
<TimerItem type="hours" />
<TimerItemLabel>Hours</TimerItemLabel>
</TimerItemGroup>
<TimerSeparator />
<TimerItemGroup>
<TimerItem type="minutes" />
<TimerItemLabel>Minutes</TimerItemLabel>
</TimerItemGroup>
<TimerSeparator />
<TimerItemGroup>
<TimerItem type="seconds" />
<TimerItemLabel>Seconds</TimerItemLabel>
</TimerItemGroup>
</TimerArea>
<TimerControl>
<TimerPlay>Go</TimerPlay>
<TimerPause>Pause</TimerPause>
<TimerReset>Reset</TimerReset>
</TimerControl>
</Timer>Controlled
Handle onTick and onComplete to react to timer progress and completion.
Orientation
Use the orientation prop on TimerItemGroup to change the orientation of the timer.
Vertical
Horizontal
Examples
Countdown
Create a countdown by setting countdown to true and startMs to the initial duration.
Date-based
Use remainingMsUntilDate to derive startMs from a calendar date.
Interval
Use the interval prop to control update frequency.
Pomodoro
Alternate between work and break sessions using onComplete.
Trigger Behavior
Triggers hide when their action is not available. To keep a control in the layout, pass hidden={false}.
Custom separator
Pass children to TimerSeparator to override the default colon (:).
API Reference
Timer
Root component. Runs a stopwatch or countdown and provides timer state to child parts.
| Prop | Type | Default |
|---|---|---|
targetMs | number | 0 |
startMs | number | 0 |
countdown | boolean | false |
interval | number | 1000 |
autoStart | boolean | false |
onTick | (details: TickDetails) => void | - |
onComplete | () => void | - |
TimerArea
Live region that exposes the formatted time to assistive tech and lays out digit groups.
| Prop | Type | Default |
|---|---|---|
className | string | - |
asChild | boolean | false |
TimerItemGroup
Groups a TimerItem with its TimerItemLabel and sets stack direction via orientation.
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical" | "vertical" |
className | string | - |
asChild | boolean | false |
TimerItemLabel
Caption for the adjacent TimerItem (for example, “minutes”).
| Prop | Type | Default |
|---|---|---|
className | string | - |
asChild | boolean | false |
TimerItem
Renders one time field from the current timer value.
| Prop | Type | Default |
|---|---|---|
type | "days" | "hours" | "minutes" | "seconds" | "milliseconds" | "seconds" |
asChild | boolean | false |
TimerSeparator
Visual delimiter between item groups (defaults to : when children are omitted).
| Prop | Type | Default |
|---|---|---|
className | string | - |
asChild | boolean | false |
TimerControl
Toolbar row for timer action controls.
| Prop | Type | Default |
|---|---|---|
className | string | - |
asChild | boolean | false |
TimerActionTrigger
Button that dispatches a timer action.
| Prop | Type | Default |
|---|---|---|
action | "start" | "pause" | "resume" | "reset" | "restart" | "start" |
hidden | boolean | - |
asChild | boolean | false |
TimerStart
Starts the timer. Wraps TimerActionTrigger with action="start".
| Prop | Type | Default |
|---|---|---|
hidden | boolean | - |
className | string | - |
asChild | boolean | false |
TimerPause
Pauses a running timer. Wraps TimerActionTrigger with action="pause".
| Prop | Type | Default |
|---|---|---|
hidden | boolean | - |
className | string | - |
asChild | boolean | false |
TimerResume
Resumes from paused. Wraps TimerActionTrigger with action="resume".
| Prop | Type | Default |
|---|---|---|
hidden | boolean | - |
className | string | - |
asChild | boolean | false |
TimerReset
Resets the timer to its initial startMs / idle state.
| Prop | Type | Default |
|---|---|---|
hidden | boolean | - |
className | string | - |
asChild | boolean | false |
TimerRestart
Restarts the timer. Wraps TimerActionTrigger with action="restart".
| Prop | Type | Default |
|---|---|---|
hidden | boolean | - |
className | string | - |
asChild | boolean | false |
TimerPlay
Shorthand for a single "go" control: renders TimerResume when the timer is paused, otherwise TimerStart.
| Prop | Type | Default |
|---|---|---|
hidden | boolean | - |
className | string | - |
asChild | boolean | false |
remainingMsUntilDate
| Parameter | Type |
|---|---|
date | Date |
Returns a number of milliseconds until date, or 0 if date is in the past.
For a complete list of props, see the Ark UI documentation.