Command Palette

Search for a command to run...

Components

Time Ring

A 24-hour dial for picking a time range, like a bedtime and wake-up schedule, with rolling digits, a tick per step, a deeper click per hour, and a thunk at the limits.

Loading…

Example

Loading…

Features

  • Picks a start and end time on a 24-hour dial, with midnight at the top. Ranges that cross midnight just work: 11:00 PM to 7:00 AM is 8 hours.
  • Drags either handle, or the arc itself to move both ends and keep the duration. Arrow keys move the focused handle by one step, and Shift moves it by an hour.
  • Snaps to 5, 15, or 30 minute steps and holds the range between minDuration and maxDuration.
  • Rolls the duration's digits in the centre as it changes, with the start and end times underneath.
  • Plays a soft tick and a selection haptic per step, a deeper click and a light haptic on each whole hour, and a thunk with a firmer haptic at the limits.
  • Sizes itself to its container, up to the size diameter, so it fits a 320 px phone. Dragging on a touch screen never scrolls the page.
  • Animates only transform, opacity, and the arc's path.

Installation

pnpm dlx shadcn@latest add @tentui/time-ring

Usage

import { TimeRing } from "@/components/time-ring";
<TimeRing
  aria-label="Sleep schedule"
  defaultValue={{ start: 23 * 60, end: 7 * 60 }}
/>

Times are minutes from midnight, from 0 to 1439. An end earlier than start falls on the next day. Use value and onValueChange to control it; onValueChange fires each time either end lands on a new step.

const [sleep, setSleep] = useState({ start: 22 * 60 + 30, end: 6 * 60 + 45 });
 
<TimeRing
  aria-label="Sleep schedule"
  value={sleep}
  onValueChange={setSleep}
  step={15}
  minDuration={6 * 60}
  maxDuration={10 * 60}
/>

minDuration and maxDuration are rounded to whole steps. The range is always at least one step long and never a whole day, so the handles never sit on top of each other.

Set format="24h" for a 24-hour clock, and replace the moon and sun with startIcon and endIcon. startLabel and endLabel name the handles for assistive technology and default to "Bedtime" and "Wake up".

<TimeRing
  aria-label="Do not disturb"
  format="24h"
  step={30}
  startIcon={<BellOff />}
  endIcon={<Coffee />}
  startLabel="Quiet from"
  endLabel="Quiet until"
/>

Give it a name to submit the range with a native form as name[start] and name[end]. The ring fills its container up to the size diameter: sm is 240 px, md 288 px, and lg 336 px. The arc color comes from the --time-ring-accent variable, which you can set through className, for example [--time-ring-accent:var(--color-sky-500)].

Interaction

InputAction
Drag a handleMoves that end, snapping to step
Drag the arcMoves both ends together and keeps the duration
↑ / →Moves the focused handle one step later
↓ / ←Moves the focused handle one step earlier
Shift + arrow, PageUp / PageDownMoves the focused handle by an hour

A handle dragged past a limit waits there until the pointer comes back around to it. The bare track between the handles does nothing, so a stray tap never moves the range. In a right-to-left layout ← and → swap, as on a native range input.

Sound and haptics

Each step plays a soft tiks click with a selection haptic, throttled to one per 35 ms so a fast drag sounds like a smooth rattle. Passing or landing on a whole hour plays a deeper click with a light haptic, and is never throttled, so you can count the hours as you drag. Reaching minDuration or maxDuration plays a low thunk with a medium haptic, and pressing an arrow key against a limit plays it again.

Sound and haptics only follow user input. The first render and changes to the value prop are silent, and nothing plays while the tab is hidden. Turn each off with sound={false} and haptics={false}.

Accessibility

The ring is a group named by aria-label or aria-labelledby. Each handle is a slider with aria-valuemin, aria-valuemax, aria-valuenow in minutes from midnight, and an aria-valuetext such as "Bedtime 11:00 PM". Both handles are described by the current duration. The labels, ticks, and readout are hidden from assistive technology. A disabled ring can't be focused or dragged.

With reduced motion enabled the handles and arc move straight to each step and the digits change without rolling. Sound and haptics stay on because they confirm each step rather than decorate movement.

API reference

PropType
PropType