DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Time Picker

Four time-picker variants — an iOS-style drum wheel, a typed spinbutton segment field, a popover trigger, and a generated time list — with 12h/24h formats, duration mode, min/max windows, per-unit steps, presets, and full keyboard and screen-reader contracts.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import { TimePicker } from "@tessinaui/ui";
{/* Inline wheel (default) */}
<TimePicker defaultValue="10:30" />

{/* Typed spinbutton segments */}
<TimePicker variant="input" defaultValue="10:30" />

{/* Field trigger + wheel popover */}
<TimePicker variant="popover" defaultValue="10:30" />

{/* Generated time list (desktop/enterprise mode, 30-min default step) */}
<TimePicker variant="list" defaultValue="10:30" />

{/* Business-hours window */}
<TimePicker minTime="09:00" maxTime="17:00" defaultValue="09:30" />

{/* Duration (0–23h span, no AM/PM, unit labels) */}
<TimePicker mode="duration" defaultValue="00:30" />

{/* Controlled */}
<TimePicker value={time} onChange={setTime} />

Examples

Default

An inline drum-roll wheel — the simplest usage.

Variants

Four display modes — wheel, input, popover, and list.

Sizes, width and labels

Shells run the Field ladder (32/36/40/48/56); the drum keeps the platform wheel metric. width widens columns, showLabels adds stationary unit labels.

Formats

12-hour (AM/PM) and 24-hour display with the format prop. The emitted value is always 24h.

With Seconds

Add a seconds column with showSeconds.

Min / max window

minTime/maxTime bound the pickable window: wheel rows stay visible as disabled options and spring back, segments clamp on commit, the list truncates.

Per-unit steps

hoursStep / minutesStep / secondsStep — each must divide 60 (the UIDatePicker rule); illegal values fall back to 1 with a dev warning.

Disabled times

shouldDisableTime(value, unit) blocks specific times; the list can drop them with hideDisabledOptions.

List mode

The generated time list — typeahead comes from the Select primitive: focus the trigger and type.

Duration

mode="duration" — a 0–23h span with forced unit labels and no AM/PM. Same "HH:mm[:ss]" wire value.

Presets and Now

Quick-pick chips (flat or grouped) plus a computed "Now" chip.

Clearable

A clear affordance while a value is set. onClear notifies controlled consumers.

In a form

name serializes the value through a hidden input for native form posts.

On color

The shells switch to the shared on-color well recipe; tone inherits from the nearest Surface.

Locales and RTL

Column/segment order follows the locale (Intl.formatToParts), never the page direction.

Live caption

The derived-value caption (alarm apps' "rings in…") is a composition, not a prop.

Bottom sheet

The mobile sheet presentation composes the house Drawer — the sheet is the plate (bordered={false}) and its footer CTA owns the commit.

Intents

Colour the selection band and field via the intent prop.

States

Read-only, disabled, and the shape-accurate loading skeleton.

Variants

VariantBehaviour
wheelInline drum-roll columns — always visible; snap-scroll, drag, tap, and full keyboard
inputTyped HH / MM (/ SS) spinbutton segments and an AM/PM toggle for 12h
popoverField-shaped trigger that opens the wheel in a named popup (focus lands on the hours column)
listField-shaped trigger that opens a generated listbox of times across the window — minutesStep defaults to 30 here

API Reference

PropTypeDefaultDescription
variant"wheel" | "input" | "popover" | "list""wheel"Display mode
mode"time" | "duration""time"Duration = 0–23h span, no AM/PM, unit labels forced
valuestring | null—Controlled value in "HH:mm" or "HH:mm:ss" 24h format
defaultValuestring | nullnullUncontrolled initial value
onChange(value: string) => void—Fires with the canonical 24h string
onClear() => void—Fires when the clear affordance empties the value
format"12h" | "24h""12h"Display format only — the value stays 24h
showSecondsbooleanfalseSeconds column / segment
hoursStepnumber1Hour interval — must divide the hour cycle
minutesStepnumber1 (list: 30)Minute interval — a divisor of 60, max 30
secondsStepnumber1Second interval — a divisor of 60, max 30
step1 | 5 | 10 | 15 | 30—Deprecated alias for minutesStep + secondsStep (one minor)
minTime / maxTimestring—Window bounds, "HH:mm[:ss]". An inverted window is ignored with a dev warning
shouldDisableTime(value, unit) => boolean—Disable specific times per unit
hideDisabledOptionsbooleanfalselist only: drop disabled options instead of rendering them disabled
presetsstring[] | {label, values}[]—Quick-pick chips in the popover/list panels
showNowbooleanfalse"Now" chip — current time snapped to steps, clamped to the window
clearablebooleanfalseClear affordance while a value is set
size"xs" | "sm" | "md" | "lg" | "xl""md"Density — shells run the Field ladder, drums the wheel metric
intent"none" | "error" | "warning" | "success" | "info""none"Semantic colour. "primary" is a deprecated alias (one minor)
rounded"none" | "sm" | "md" | "lg" | "full""md"Shells use the control scale (full = pill); wheel/popup panels cap full at rounded-3xl
tone"default" | "on-color"inheritedOn-color well recipe on coloured surfaces
width"narrow" | "default" | "wide""default"Wheel column width preset
borderedbooleantrueWheel only: paint the drum's own plate (false when a panel already provides one)
disabledbooleanfalseToken-swap disable (no opacity fade)
readOnlybooleanfalseValue locked; popover/list stay closed, focus preserved
dir"ltr" | "rtl""ltr"Chrome direction — column order comes from locale, not from this
localestring"en-US" ("ar" when rtl)Decides segment/column ORDER and AM/PM glyphs
stringsPartial<TimePickerStrings>—Override any built-in label/aria string
placeholderstring"--:-- --" / "--:--"Empty display in triggers
namestring—Serializes the value via a hidden input
open / defaultOpen / onOpenChange——Controlled popover/list open state
skeletonbooleanfalseShape-accurate placeholder — forwards format/seconds/labels/width
showLabelsbooleanfalseStationary hours/min/sec labels beside the selected numbers
classNamestring—Classes on the root wrapper

Accessibility

  • Drum columns are role="listbox" with one tab stop each, aria-activedescendant tracking the active row, aria-selected on the committed option, and aria-disabled on out-of-window rows (visible, non-committable, sprung back from). Keyboard: ArrowUp/Down ±1, PageUp/Down ±5, Home/End — all seek past disabled rows.
  • Segments are role="spinbutton" with aria-valuenow/min/max/text (a localized "Empty" while placeholder), typed digits with early auto-advance, Backspace digit-strip then previous-segment, ArrowLeft/Right between segments (visually flipped in RTL), Home/End to min/max, and A/P on the period toggle. On iOS the role flips to textbox — VoiceOver there cannot focus spinbuttons (the React Aria carve-out).
  • Popover popups are named, focus the hours column on open, close on Enter/Escape, and return focus to the trigger. The inner wheel demotes to presentation so the widget announces once.
  • aria-* passed to the root lands on the element carrying the role (§7 split); data-* stays on the wrapper.

Notes

  • Value format — always stored and emitted in 24h "HH:mm" (or "HH:mm:ss"). format only changes the display; duration mode shares the same wire format (≤ 23:59).
  • Commit model — inline surfaces apply instantly (the platform consensus); the drum commits ~100ms after the scroll settles. A confirmation footer belongs to a sheet/dialog composition (see Bottom sheet), not to the component.
  • Locale, not direction — segment and column order come from the locale's Intl.formatToParts; dir mirrors surrounding chrome only, and digits render as a bidi-isolated LTR run.
  • Boundaries — availability slots (chip grids, delivery windows, day strips, ASAP rows) are a separate component (see the time-slots tracker); date+time together is DatePicker/Calendar's job (Calendar composes this wheel); circular bedtime-style range dials compose CircularSlider; timezone selection composes Select/Combobox.
  • Wheel metrics — drum rows keep the platform 32–48px heights (scroll-first surfaces; each column is a ≥44px-wide touch target). Shell variants carry the standard phone floors.
TextareaToast

On this page

PlaygroundInstallationUsageExamplesDefaultVariantsSizes, width and labelsFormatsWith SecondsMin / max windowPer-unit stepsDisabled timesList modeDurationPresets and NowClearableIn a formOn colorLocales and RTLLive captionBottom sheetIntentsStatesVariantsAPI ReferenceAccessibilityNotes