Timer

PreviousNext

A flexible timer component system with compound components for displaying elapsed time. Features multiple variants, sizes, formats, and a custom hook for advanced timer functionality.

Installation

pnpm dlx shadcn@latest add https://cult-ui.com/r/timer.json

Usage

import { Timer, TimerDisplay, TimerIcon, TimerRoot, useTimer, } from "@/components/ui/timer"

Basic Timer

<Timer loading={true} />

Compound Components

For more flexibility, you can use the individual components:

<TimerRoot variant="outline" size="lg"> <TimerIcon loading={true} /> <TimerDisplay time="01:23" /> </TimerRoot>

Timer with Custom Format

<Timer loading={true} format="MM:SS" variant="outline" size="lg" />

Timer with Callback

const [isLoading, setIsLoading] = useState(false) const handleTick = (seconds: number, milliseconds: number) => { console.log(`Elapsed: ${seconds}.${milliseconds}s`) } return ( <div> <Timer loading={isLoading} onTick={handleTick} format="SS.MS" variant="outline" /> <button onClick={() => setIsLoading(!isLoading)}> {isLoading ? "Stop" : "Start"} </button> </div> )

Different Formats

// Seconds with milliseconds <Timer loading={true} format="SS.MS" /> // Minutes:Seconds <Timer loading={true} format="MM:SS" /> // Hours:Minutes:Seconds <Timer loading={true} format="HH:MM:SS" />

API Reference

Timer Props

PropTypeDefaultDescription
loadingbooleanfalseWhether the timer is running/loading
onTick(seconds: number, milliseconds: number) => void-Callback called on each timer tick
resetOnLoadingChangebooleantrueWhether to reset timer when loading changes
format"SS.MS" | "MM:SS" | "HH:MM:SS""SS.MS"Time display format
variant"default" | "outline" | "ghost" | "destructive""default"Timer variant
size"sm" | "md" | "lg""md"Timer size
classNamestring-Additional CSS classes

TimerRoot Props

PropTypeDefaultDescription
variant"default" | "outline" | "ghost" | "destructive""default"Timer container variant
size"sm" | "md" | "lg""md"Timer size
loadingbooleanfalseWhether the timer is running
classNamestring-Additional CSS classes

TimerIcon Props

PropTypeDefaultDescription
size"sm" | "md" | "lg""md"Icon size
loadingbooleanfalseWhether to show loading state
iconReact.ComponentType<{ className?: string }>ClockCustom icon component
classNamestring-Additional CSS classes

TimerDisplay Props

PropTypeDefaultDescription
timestring-Time value to display
labelstring-Optional label for accessibility
size"sm" | "md" | "lg""md"Display size
classNamestring-Additional CSS classes

useTimer Hook

The Timer component uses the useTimer hook internally, which you can also use directly:

const { elapsedTime, milliseconds, formattedTime, isRunning, reset, start, stop, } = useTimer({ loading: true, onTick: (seconds, ms) => console.log(`${seconds}.${ms}s`), format: "SS.MS", })

useTimer Options

OptionTypeDefaultDescription
loadingbooleanfalseWhether the timer is running
onTick(seconds: number, milliseconds: number) => void-Callback called on each timer tick
resetOnLoadingChangebooleantrueWhether to reset timer when loading changes
format"SS.MS" | "MM:SS" | "HH:MM:SS""SS.MS"Time display format

useTimer Return

PropertyTypeDescription
elapsedTimenumberElapsed time in seconds
millisecondsnumberCurrent milliseconds (0-999)
formattedTimeobjectFormatted time object with display
isRunningbooleanWhether the timer is currently running
reset() => voidReset the timer to 0
start() => voidStart the timer
stop() => voidStop the timer

Examples

Loading State Timer

const [isLoading, setIsLoading] = useState(false) return ( <div className="flex items-center gap-4"> <Timer loading={isLoading} variant="outline" size="lg" format="SS.MS" /> <button onClick={() => setIsLoading(!isLoading)}> {isLoading ? "Stop" : "Start"} </button> </div> )

Timer with Progress Tracking

const [isLoading, setIsLoading] = useState(false) const [elapsedTime, setElapsedTime] = useState(0) const handleTick = (seconds: number, milliseconds: number) => { setElapsedTime(seconds) // Update progress bar, send analytics, etc. } return ( <div className="space-y-4"> <Timer loading={isLoading} onTick={handleTick} variant="ghost" size="lg" format="MM:SS" /> <div className="text-sm text-muted-foreground"> Elapsed: {elapsedTime} seconds </div> <button onClick={() => setIsLoading(!isLoading)}> {isLoading ? "Stop" : "Start"} </button> </div> )

Different Time Formats

// For short durations (seconds with milliseconds) <Timer loading={true} format="SS.MS" variant="default" /> // For medium durations (minutes:seconds) <Timer loading={true} format="MM:SS" variant="outline" /> // For long durations (hours:minutes:seconds) <Timer loading={true} format="HH:MM:SS" variant="ghost" />

Custom Timer with Compound Components

function CustomTimer() { const { formattedTime, isRunning, start, stop, reset } = useTimer({ format: "MM:SS", }) return ( <div className="flex items-center gap-4"> <TimerRoot variant="outline" size="lg"> <TimerIcon loading={isRunning} /> <TimerDisplay time={formattedTime.display} /> </TimerRoot> <div className="flex gap-2"> <button onClick={isRunning ? stop : start}> {isRunning ? ( <Pause className="w-4 h-4" /> ) : ( <Play className="w-4 h-4" /> )} </button> <button onClick={reset}> <RotateCcw className="w-4 h-4" /> </button> </div> </div> ) }

Custom Icon

<TimerRoot variant="destructive" size="lg"> <TimerIcon loading={true} icon={Stopwatch} /> <TimerDisplay time="02:15" /> </TimerRoot>

Using the Hook Directly

function CustomTimer() { const { elapsedTime, formattedTime, isRunning, start, stop, reset } = useTimer({ loading: false, format: "MM:SS", }) return ( <div className="space-y-4"> <div className="text-2xl font-mono">{formattedTime.display}</div> <div className="flex gap-2"> <button onClick={isRunning ? stop : start}> {isRunning ? "Stop" : "Start"} </button> <button onClick={reset}>Reset</button> </div> </div> ) }