Built on Vercel’s UI SDK 0.1.0-beta.2. Its API may change.
Time input
A time field on Vercel's UI SDK typed one part at a time, on a 12 or 24-hour clock with optional seconds, min and max, and a 24-hour value for forms.
- Form components
- 3 parts
- 10 examples
@ui-sdk/core@ui-sdk/react
pnpm dlx shadcn@latest add https://hextaui.com/r/ui-sdk-time-input.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
Add the theme tokens to your global CSS, if you haven’t yet.
Install the dependencies.
pnpm add @ui-sdk/react@0.1.0-beta.2 @ui-sdk/core@0.1.0-beta.2 cnCopy and paste the following code into your project.
components/ui/ui-sdk-time-input.tsx Update the import paths to match your project setup.
UI SDK adds a little CSS of its own in a cascade layer named ui-sdk. Declare that layer at the top of your global stylesheet, above the Tailwind import, so your classes win over it.
The value is a 24-hour time like "14:30", the same as a native time input’s, whatever clock the field shows. It stays null until every part holds something. Each part is its own small input: type digits and it moves on, or press A or P for the half of the day.
The field is a group, so name it with aria-labelledby or aria-label. Clicking anywhere in the box focuses the nearest part.
TimeInput renders its parts on its own. Add TimeInputSegments yourself to put an icon or other content beside them.
The field shows the locale’s clock. hourCycle picks 12 or 24 hours, and granularity adds seconds or shows the hour alone. All of them submit 24-hour values.
A time before min or after max marks the field invalid, and its form won’t submit.
invalid marks the field for checks of your own, like an end time before the start.
locale sets the order, the separators and the names for the half of the day. Without it, the field follows the page’s language once it’s interactive.
A hidden input submits the time under name. required stops an empty or half-typed time, and a reset brings back the default.
createTimeInputController() sets, clears or focuses the field from anywhere. Focus a part by its unit, like "minute".
A disabled field is skipped by focus and doesn’t submit.
A read-only field takes focus and submits, but keys and typing don’t change it.
The parts keep the locale’s order, and the arrow keys follow them on screen.
| Key | Action |
|---|---|
| 0–9 | Fills the part, then moves to the next one. |
| ↑↓ | Steps the part by one, wrapping around. An empty part starts at the current time. |
| PageUpPageDown | Steps by more, like 15 minutes. |
| HomeEnd | Goes to the part’s first or last value. |
| ←→ | Moves to the part before or after. |
| Backspace | Removes the last digit, then empties the part, then moves back. |
| AP | Chooses AM or PM on a 12-hour clock. |
| Enter | Submits the form with what’s typed. |
The root is a group. Each part is a spinbutton named by its unit, like “hour”, with its value and range, and an empty part reads as “Empty”. The text between parts is hidden from screen readers.
Parts are real inputs, so phones show a number pad and typing works before the page’s script runs. They grow to 16px on touch screens so phones don’t zoom in, and the focused part is highlighted rather than showing a caret.
Each part wraps the UI SDK part of the same name and accepts all of its props, including render and a className that can be a function of the part’s state.
| Prop | Type | Default |
|---|---|---|
value | string | null | – |
defaultValue | string | null | – |
onValueChangeRuns once every part holds something, and with null when one is emptied. | (value: string | null, details) => void | – |
hourCycleDefaults to the locale’s clock. | 12 | 24 | – |
granularity | "hour" | "minute" | "second" | "minute" |
min | string | – |
max | string | – |
localeDefaults to the page’s language. | string | – |
invalid | boolean | false |
disabled | boolean | false |
readOnly | boolean | false |
required | boolean | false |
name | string | – |
form | string | – |
controllerFrom createTimeInputController(). | TimeInputController | – |
childrenDefaults to TimeInputSegments. | ReactNode | (state) => ReactNode | – |
render | ReactElement | (props, state) => ReactElement | – |
| Attribute | Description |
|---|---|
data-slot="time-input" | Target the time input in CSS. |
data-invalid | Present when the time is outside min and max, or invalid is set. |
data-empty | Present while every part is empty. |
data-disabled | Present when disabled. |
data-readonly | Present when read-only. |
data-required | Present when required. |
Renders a TimeInputSegment for each part, in the locale’s order, with the text between them.
| Prop | Type | Default |
|---|---|---|
segmentOne of the root state’s segments. | TimeInputSegmentData | – |
render | ReactElement | (props, state) => ReactElement | – |
| Attribute | Description |
|---|---|
data-slot="time-input-segment" | Target a part in CSS. |
data-type | hour, minute, second, dayPeriod, or literal for the text between parts. |
data-empty | Present while the part is empty. |
- CalendarA date grid for single, range and multiple selection, with sliding months, range previews and touch-sized days.
- CheckboxA checkbox whose check draws in, with indeterminate parents, groups and labels that share its hover.
- ComboboxA filterable select with chips, groups and async results, in a popup that resizes as you type.
- Date pickerA button that opens a calendar in a popover, or a bottom sheet on phones, for single dates and ranges.
- FieldLabels, descriptions and errors wired to their control, with validation states and layouts for forms.
- InputA text input with three sizes, invalid and read-only states, native validation styling and a 16px touch font so phones never zoom in.