Relative Time
Multi-timezone time display component for React and Next.js applications. Built with TypeScript support, Tailwind CSS styling, and shadcn/ui design featuring auto-updates, custom formatting, and controlled time states.
'use client';import { RelativeTime, RelativeTimeZone, RelativeTimeZoneDate, RelativeTimeZoneDisplay, RelativeTimeZoneLabel,} from '@/components/ui/shadcn-io/relative-time';const timezones = [ { label: 'EST', zone: 'America/New_York' }, { label: 'GMT', zone: 'Europe/London' }, { label: 'JST', zone: 'Asia/Tokyo' },];const Example = () => ( <div className="rounded-md border bg-background p-4"> <RelativeTime> {timezones.map(({ zone, label }) => ( <RelativeTimeZone key={zone} zone={zone}> <RelativeTimeZoneLabel>{label}</RelativeTimeZoneLabel> <RelativeTimeZoneDate /> <RelativeTimeZoneDisplay /> </RelativeTimeZone> ))} </RelativeTime> </div>);export default Example;
'use client';import { useControllableState } from '@radix-ui/react-use-controllable-state';import { createContext, type HTMLAttributes, useContext, useEffect,} from 'react';import { cn } from '@/lib/utils';const formatDate = ( date: Date, timeZone: string, options?: Intl.DateTimeFormatOptions) => new Intl.DateTimeFormat( 'en-US', options ?? { dateStyle: 'long', timeZone, } ).format(date);const formatTime = ( date: Date, timeZone: string, options?: Intl.DateTimeFormatOptions) => new Intl.DateTimeFormat( 'en-US', options ?? { hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone, } ).format(date);type RelativeTimeContextType = { time: Date; dateFormatOptions?: Intl.DateTimeFormatOptions; timeFormatOptions?: Intl.DateTimeFormatOptions;};const RelativeTimeContext = createContext<RelativeTimeContextType>({ time: new Date(), dateFormatOptions: { dateStyle: 'long', }, timeFormatOptions: { hour: '2-digit', minute: '2-digit', },});export type RelativeTimeProps = HTMLAttributes<HTMLDivElement> & { time?: Date; defaultTime?: Date; onTimeChange?: (time: Date) => void; dateFormatOptions?: Intl.DateTimeFormatOptions; timeFormatOptions?: Intl.DateTimeFormatOptions;};export const RelativeTime = ({ time: controlledTime, defaultTime = new Date(), onTimeChange, dateFormatOptions, timeFormatOptions, className, ...props}: RelativeTimeProps) => { const [time, setTime] = useControllableState<Date>({ defaultProp: defaultTime, prop: controlledTime, onChange: onTimeChange, }); useEffect(() => { if (controlledTime) { return; } const interval = setInterval(() => { setTime(new Date()); }, 1000); return () => clearInterval(interval); }, [setTime, controlledTime]); return ( <RelativeTimeContext.Provider value={{ time: time ?? defaultTime, dateFormatOptions, timeFormatOptions, }} > <div className={cn('grid gap-2', className)} {...props} /> </RelativeTimeContext.Provider> );};export type RelativeTimeZoneProps = HTMLAttributes<HTMLDivElement> & { zone: string; dateFormatOptions?: Intl.DateTimeFormatOptions; timeFormatOptions?: Intl.DateTimeFormatOptions;};export type RelativeTimeZoneContextType = { zone: string;};const RelativeTimeZoneContext = createContext<RelativeTimeZoneContextType>({ zone: 'UTC',});export const RelativeTimeZone = ({ zone, className, ...props}: RelativeTimeZoneProps) => ( <RelativeTimeZoneContext.Provider value={{ zone }}> <div className={cn( 'flex items-center justify-between gap-1.5 text-xs', className )} {...props} /> </RelativeTimeZoneContext.Provider>);export type RelativeTimeZoneDisplayProps = HTMLAttributes<HTMLDivElement>;export const RelativeTimeZoneDisplay = ({ className, ...props}: RelativeTimeZoneDisplayProps) => { const { time, timeFormatOptions } = useContext(RelativeTimeContext); const { zone } = useContext(RelativeTimeZoneContext); const display = formatTime(time, zone, timeFormatOptions); return ( <div className={cn('pl-8 text-muted-foreground tabular-nums', className)} {...props} > {display} </div> );};export type RelativeTimeZoneDateProps = HTMLAttributes<HTMLDivElement>;export const RelativeTimeZoneDate = ({ className, ...props}: RelativeTimeZoneDateProps) => { const { time, dateFormatOptions } = useContext(RelativeTimeContext); const { zone } = useContext(RelativeTimeZoneContext); const display = formatDate(time, zone, dateFormatOptions); return <div {...props}>{display}</div>;};export type RelativeTimeZoneLabelProps = HTMLAttributes<HTMLDivElement>;export const RelativeTimeZoneLabel = ({ className, ...props}: RelativeTimeZoneLabelProps) => ( <div className={cn( 'flex h-4 items-center justify-center rounded-xs bg-secondary px-1.5 font-mono', className )} {...props} />);
Installation
npx shadcn@latest add https://www.shadcn.io/registry/relative-time.json
npx shadcn@latest add https://www.shadcn.io/registry/relative-time.json
pnpm dlx shadcn@latest add https://www.shadcn.io/registry/relative-time.json
bunx shadcn@latest add https://www.shadcn.io/registry/relative-time.json
Features
- Multi-timezone display - Simultaneous time zones with labeled formatting using JavaScript Date APIs
- Auto-updating - Real-time updates every second with controlled state options for React applications
- Custom formatting - Configurable date and time display patterns using TypeScript props
- Controlled/uncontrolled - Flexible state management for time manipulation in Next.js projects
- Responsive layout - Flex positioning with clean minimal UI using Tailwind CSS utilities
- Performance optimized - Efficient interval management with proper cleanup using shadcn/ui patterns
- Open source - Free timezone component with customizable appearance and formatting
Examples
Custom date format
'use client';import { RelativeTime, RelativeTimeZone, RelativeTimeZoneDate, RelativeTimeZoneDisplay, RelativeTimeZoneLabel,} from '@/components/ui/shadcn-io/relative-time';const timezones = [ { label: 'EST', zone: 'America/New_York' }, { label: 'GMT', zone: 'Europe/London' }, { label: 'JST', zone: 'Asia/Tokyo' },];const Example = () => ( <div className="rounded-md border bg-background p-4"> <RelativeTime dateFormatOptions={{ dateStyle: 'full' }}> {timezones.map(({ zone, label }) => ( <RelativeTimeZone key={zone} zone={zone}> <RelativeTimeZoneLabel>{label}</RelativeTimeZoneLabel> <RelativeTimeZoneDate /> <RelativeTimeZoneDisplay /> </RelativeTimeZone> ))} </RelativeTime> </div>);export default Example;
'use client';import { useControllableState } from '@radix-ui/react-use-controllable-state';import { createContext, type HTMLAttributes, useContext, useEffect,} from 'react';import { cn } from '@/lib/utils';const formatDate = ( date: Date, timeZone: string, options?: Intl.DateTimeFormatOptions) => new Intl.DateTimeFormat( 'en-US', options ?? { dateStyle: 'long', timeZone, } ).format(date);const formatTime = ( date: Date, timeZone: string, options?: Intl.DateTimeFormatOptions) => new Intl.DateTimeFormat( 'en-US', options ?? { hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone, } ).format(date);type RelativeTimeContextType = { time: Date; dateFormatOptions?: Intl.DateTimeFormatOptions; timeFormatOptions?: Intl.DateTimeFormatOptions;};const RelativeTimeContext = createContext<RelativeTimeContextType>({ time: new Date(), dateFormatOptions: { dateStyle: 'long', }, timeFormatOptions: { hour: '2-digit', minute: '2-digit', },});export type RelativeTimeProps = HTMLAttributes<HTMLDivElement> & { time?: Date; defaultTime?: Date; onTimeChange?: (time: Date) => void; dateFormatOptions?: Intl.DateTimeFormatOptions; timeFormatOptions?: Intl.DateTimeFormatOptions;};export const RelativeTime = ({ time: controlledTime, defaultTime = new Date(), onTimeChange, dateFormatOptions, timeFormatOptions, className, ...props}: RelativeTimeProps) => { const [time, setTime] = useControllableState<Date>({ defaultProp: defaultTime, prop: controlledTime, onChange: onTimeChange, }); useEffect(() => { if (controlledTime) { return; } const interval = setInterval(() => { setTime(new Date()); }, 1000); return () => clearInterval(interval); }, [setTime, controlledTime]); return ( <RelativeTimeContext.Provider value={{ time: time ?? defaultTime, dateFormatOptions, timeFormatOptions, }} > <div className={cn('grid gap-2', className)} {...props} /> </RelativeTimeContext.Provider> );};export type RelativeTimeZoneProps = HTMLAttributes<HTMLDivElement> & { zone: string; dateFormatOptions?: Intl.DateTimeFormatOptions; timeFormatOptions?: Intl.DateTimeFormatOptions;};export type RelativeTimeZoneContextType = { zone: string;};const RelativeTimeZoneContext = createContext<RelativeTimeZoneContextType>({ zone: 'UTC',});export const RelativeTimeZone = ({ zone, className, ...props}: RelativeTimeZoneProps) => ( <RelativeTimeZoneContext.Provider value={{ zone }}> <div className={cn( 'flex items-center justify-between gap-1.5 text-xs', className )} {...props} /> </RelativeTimeZoneContext.Provider>);export type RelativeTimeZoneDisplayProps = HTMLAttributes<HTMLDivElement>;export const RelativeTimeZoneDisplay = ({ className, ...props}: RelativeTimeZoneDisplayProps) => { const { time, timeFormatOptions } = useContext(RelativeTimeContext); const { zone } = useContext(RelativeTimeZoneContext); const display = formatTime(time, zone, timeFormatOptions); return ( <div className={cn('pl-8 text-muted-foreground tabular-nums', className)} {...props} > {display} </div> );};export type RelativeTimeZoneDateProps = HTMLAttributes<HTMLDivElement>;export const RelativeTimeZoneDate = ({ className, ...props}: RelativeTimeZoneDateProps) => { const { time, dateFormatOptions } = useContext(RelativeTimeContext); const { zone } = useContext(RelativeTimeZoneContext); const display = formatDate(time, zone, dateFormatOptions); return <div {...props}>{display}</div>;};export type RelativeTimeZoneLabelProps = HTMLAttributes<HTMLDivElement>;export const RelativeTimeZoneLabel = ({ className, ...props}: RelativeTimeZoneLabelProps) => ( <div className={cn( 'flex h-4 items-center justify-center rounded-xs bg-secondary px-1.5 font-mono', className )} {...props} />);
Custom time format
'use client';import { RelativeTime, RelativeTimeZone, RelativeTimeZoneDate, RelativeTimeZoneDisplay, RelativeTimeZoneLabel,} from '@/components/ui/shadcn-io/relative-time';const timezones = [ { label: 'EST', zone: 'America/New_York' }, { label: 'GMT', zone: 'Europe/London' }, { label: 'JST', zone: 'Asia/Tokyo' },];const Example = () => ( <div className="rounded-md border bg-background p-4"> <RelativeTime timeFormatOptions={{ hour: '2-digit', minute: '2-digit' }}> {timezones.map(({ zone, label }) => ( <RelativeTimeZone key={zone} zone={zone}> <RelativeTimeZoneLabel>{label}</RelativeTimeZoneLabel> <RelativeTimeZoneDate /> <RelativeTimeZoneDisplay /> </RelativeTimeZone> ))} </RelativeTime> </div>);export default Example;
'use client';import { useControllableState } from '@radix-ui/react-use-controllable-state';import { createContext, type HTMLAttributes, useContext, useEffect,} from 'react';import { cn } from '@/lib/utils';const formatDate = ( date: Date, timeZone: string, options?: Intl.DateTimeFormatOptions) => new Intl.DateTimeFormat( 'en-US', options ?? { dateStyle: 'long', timeZone, } ).format(date);const formatTime = ( date: Date, timeZone: string, options?: Intl.DateTimeFormatOptions) => new Intl.DateTimeFormat( 'en-US', options ?? { hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone, } ).format(date);type RelativeTimeContextType = { time: Date; dateFormatOptions?: Intl.DateTimeFormatOptions; timeFormatOptions?: Intl.DateTimeFormatOptions;};const RelativeTimeContext = createContext<RelativeTimeContextType>({ time: new Date(), dateFormatOptions: { dateStyle: 'long', }, timeFormatOptions: { hour: '2-digit', minute: '2-digit', },});export type RelativeTimeProps = HTMLAttributes<HTMLDivElement> & { time?: Date; defaultTime?: Date; onTimeChange?: (time: Date) => void; dateFormatOptions?: Intl.DateTimeFormatOptions; timeFormatOptions?: Intl.DateTimeFormatOptions;};export const RelativeTime = ({ time: controlledTime, defaultTime = new Date(), onTimeChange, dateFormatOptions, timeFormatOptions, className, ...props}: RelativeTimeProps) => { const [time, setTime] = useControllableState<Date>({ defaultProp: defaultTime, prop: controlledTime, onChange: onTimeChange, }); useEffect(() => { if (controlledTime) { return; } const interval = setInterval(() => { setTime(new Date()); }, 1000); return () => clearInterval(interval); }, [setTime, controlledTime]); return ( <RelativeTimeContext.Provider value={{ time: time ?? defaultTime, dateFormatOptions, timeFormatOptions, }} > <div className={cn('grid gap-2', className)} {...props} /> </RelativeTimeContext.Provider> );};export type RelativeTimeZoneProps = HTMLAttributes<HTMLDivElement> & { zone: string; dateFormatOptions?: Intl.DateTimeFormatOptions; timeFormatOptions?: Intl.DateTimeFormatOptions;};export type RelativeTimeZoneContextType = { zone: string;};const RelativeTimeZoneContext = createContext<RelativeTimeZoneContextType>({ zone: 'UTC',});export const RelativeTimeZone = ({ zone, className, ...props}: RelativeTimeZoneProps) => ( <RelativeTimeZoneContext.Provider value={{ zone }}> <div className={cn( 'flex items-center justify-between gap-1.5 text-xs', className )} {...props} /> </RelativeTimeZoneContext.Provider>);export type RelativeTimeZoneDisplayProps = HTMLAttributes<HTMLDivElement>;export const RelativeTimeZoneDisplay = ({ className, ...props}: RelativeTimeZoneDisplayProps) => { const { time, timeFormatOptions } = useContext(RelativeTimeContext); const { zone } = useContext(RelativeTimeZoneContext); const display = formatTime(time, zone, timeFormatOptions); return ( <div className={cn('pl-8 text-muted-foreground tabular-nums', className)} {...props} > {display} </div> );};export type RelativeTimeZoneDateProps = HTMLAttributes<HTMLDivElement>;export const RelativeTimeZoneDate = ({ className, ...props}: RelativeTimeZoneDateProps) => { const { time, dateFormatOptions } = useContext(RelativeTimeContext); const { zone } = useContext(RelativeTimeZoneContext); const display = formatDate(time, zone, dateFormatOptions); return <div {...props}>{display}</div>;};export type RelativeTimeZoneLabelProps = HTMLAttributes<HTMLDivElement>;export const RelativeTimeZoneLabel = ({ className, ...props}: RelativeTimeZoneLabelProps) => ( <div className={cn( 'flex h-4 items-center justify-center rounded-xs bg-secondary px-1.5 font-mono', className )} {...props} />);
Controlled time
'use client';import { RelativeTime, RelativeTimeZone, RelativeTimeZoneDate, RelativeTimeZoneDisplay, RelativeTimeZoneLabel,} from '@/components/ui/shadcn-io/relative-time';import { useEffect, useState } from 'react';const timezones = [ { label: 'EST', zone: 'America/New_York' }, { label: 'GMT', zone: 'Europe/London' }, { label: 'JST', zone: 'Asia/Tokyo' },];const Example = () => { const [time, setTime] = useState(new Date()); useEffect(() => { const interval = setInterval(() => { setTime(new Date()); }, 1000); return () => clearInterval(interval); }, []); return ( <div className="rounded-md border bg-background p-4"> <RelativeTime onTimeChange={setTime} time={time}> {timezones.map(({ zone, label }) => ( <RelativeTimeZone key={zone} zone={zone}> <RelativeTimeZoneLabel>{label}</RelativeTimeZoneLabel> <RelativeTimeZoneDate /> <RelativeTimeZoneDisplay /> </RelativeTimeZone> ))} </RelativeTime> </div> );};export default Example;
'use client';import { useControllableState } from '@radix-ui/react-use-controllable-state';import { createContext, type HTMLAttributes, useContext, useEffect,} from 'react';import { cn } from '@/lib/utils';const formatDate = ( date: Date, timeZone: string, options?: Intl.DateTimeFormatOptions) => new Intl.DateTimeFormat( 'en-US', options ?? { dateStyle: 'long', timeZone, } ).format(date);const formatTime = ( date: Date, timeZone: string, options?: Intl.DateTimeFormatOptions) => new Intl.DateTimeFormat( 'en-US', options ?? { hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone, } ).format(date);type RelativeTimeContextType = { time: Date; dateFormatOptions?: Intl.DateTimeFormatOptions; timeFormatOptions?: Intl.DateTimeFormatOptions;};const RelativeTimeContext = createContext<RelativeTimeContextType>({ time: new Date(), dateFormatOptions: { dateStyle: 'long', }, timeFormatOptions: { hour: '2-digit', minute: '2-digit', },});export type RelativeTimeProps = HTMLAttributes<HTMLDivElement> & { time?: Date; defaultTime?: Date; onTimeChange?: (time: Date) => void; dateFormatOptions?: Intl.DateTimeFormatOptions; timeFormatOptions?: Intl.DateTimeFormatOptions;};export const RelativeTime = ({ time: controlledTime, defaultTime = new Date(), onTimeChange, dateFormatOptions, timeFormatOptions, className, ...props}: RelativeTimeProps) => { const [time, setTime] = useControllableState<Date>({ defaultProp: defaultTime, prop: controlledTime, onChange: onTimeChange, }); useEffect(() => { if (controlledTime) { return; } const interval = setInterval(() => { setTime(new Date()); }, 1000); return () => clearInterval(interval); }, [setTime, controlledTime]); return ( <RelativeTimeContext.Provider value={{ time: time ?? defaultTime, dateFormatOptions, timeFormatOptions, }} > <div className={cn('grid gap-2', className)} {...props} /> </RelativeTimeContext.Provider> );};export type RelativeTimeZoneProps = HTMLAttributes<HTMLDivElement> & { zone: string; dateFormatOptions?: Intl.DateTimeFormatOptions; timeFormatOptions?: Intl.DateTimeFormatOptions;};export type RelativeTimeZoneContextType = { zone: string;};const RelativeTimeZoneContext = createContext<RelativeTimeZoneContextType>({ zone: 'UTC',});export const RelativeTimeZone = ({ zone, className, ...props}: RelativeTimeZoneProps) => ( <RelativeTimeZoneContext.Provider value={{ zone }}> <div className={cn( 'flex items-center justify-between gap-1.5 text-xs', className )} {...props} /> </RelativeTimeZoneContext.Provider>);export type RelativeTimeZoneDisplayProps = HTMLAttributes<HTMLDivElement>;export const RelativeTimeZoneDisplay = ({ className, ...props}: RelativeTimeZoneDisplayProps) => { const { time, timeFormatOptions } = useContext(RelativeTimeContext); const { zone } = useContext(RelativeTimeZoneContext); const display = formatTime(time, zone, timeFormatOptions); return ( <div className={cn('pl-8 text-muted-foreground tabular-nums', className)} {...props} > {display} </div> );};export type RelativeTimeZoneDateProps = HTMLAttributes<HTMLDivElement>;export const RelativeTimeZoneDate = ({ className, ...props}: RelativeTimeZoneDateProps) => { const { time, dateFormatOptions } = useContext(RelativeTimeContext); const { zone } = useContext(RelativeTimeZoneContext); const display = formatDate(time, zone, dateFormatOptions); return <div {...props}>{display}</div>;};export type RelativeTimeZoneLabelProps = HTMLAttributes<HTMLDivElement>;export const RelativeTimeZoneLabel = ({ className, ...props}: RelativeTimeZoneLabelProps) => ( <div className={cn( 'flex h-4 items-center justify-center rounded-xs bg-secondary px-1.5 font-mono', className )} {...props} />);
Use Cases
- Global applications - Multi-region time displays for international teams
- Meeting schedulers - Timezone-aware appointment and event planning
- Dashboard widgets - Real-time clock displays for monitoring interfaces
- Travel applications - Multiple location time tracking for itineraries
Implementation
Built with native Date APIs for timezone handling. Uses setInterval for auto-updates with proper cleanup. Supports custom formatters and controlled time states. Responsive design with minimal UI.
Mini Calendar
Compact date picker component for React and Next.js applications. Built with TypeScript support, Tailwind CSS styling, and shadcn/ui design featuring horizontal layout, navigation controls, and accessibility features.
Comparison
Interactive slider comparison component for React and Next.js applications. Built with TypeScript support, Tailwind CSS styling, and shadcn/ui design featuring drag controls, hover modes, and smooth animations.