Input Duration
Input that handles duration in total seconds for controlled or native forms.
Overview
InputDuration composes Input Group only (no calendar). The visible value is split into
focusable segments—one per field in your format. The API uses total seconds (number | null).
A hidden <input> mirrors the seconds string for native forms. Commit occurs on segment
blur or Enter; onValueChange and normalization run then.
Within each segment, digits accumulate right-to-left. Segments are uncapped until commit. On
commit, values normalize with overflow carried into larger units that exist in format (60/24
rules for time; approximate 30-day months and 365-day years for y / M / d—see
lib/duration-format.ts). If a larger unit is absent from the pattern, the smaller segment does
not carry upward.
Editable fields use y, M, d, H, m, s (runs set minimum width); other characters are
static labels; use single quotes for literals. Default: HH'h' mm'm' ss's'.
Key bindings
| Key | Behavior |
|---|---|
| ⌫ | Deletes the last digit in the focused segment. If the segment is empty, focus moves to the previous segment. |
| ⌦ | Clears the focused segment. The next digit typed replaces the whole segment. |
| ←/→ | Move focus to the previous or next segment (at the ends, the key has no effect). |
| ↵ | Commits and normalizes the duration. Works from a segment or from the surrounding control. |
| ⇥ | Moves through segments in focus order. |
blur | Leaving a segment commits with the same normalization as ↵. |
Custom props
| Prop | Type | Description |
|---|---|---|
format | string | Pattern for segments and static text. Default: HH'h' mm'm' ss's'. Use single-quoted literals for static text; see Overview. |
value | number | null | Controlled total duration in seconds, or null for empty. |
onValueChange | (seconds: number | null) => void | Fires after blur of a segment or Enter, with normalized carry; null when all segments are cleared. |
size | 'xs' | 'sm' | 'default' | 'lg' | Optional. Input Group size: shell height and segment padding/text scale together. This is not the HTML size attribute (that prop is not forwarded to the hidden input). |
Install
Configure your registry in components.json:
{
"registries": {
"@uxio": "https://ui.uxio.dev/r/styles/{style}/{name}.json"
}
}Then run:
npx shadcn@latest add @uxio/input-durationThis pulls input-group as a registry dependency. The correct variant (Base UI or Radix) follows
your components.json style. The installed bundle includes lib/datetime-format and
lib/duration-format.
Usage
Controlled duration (seconds)
import * as React from "react"
import { InputDuration } from "@/components/ui/input-duration"
export function DurationField() {
const [value, setValue] = React.useState<number | null>(null)
return <InputDuration value={value} onValueChange={setValue} />
}Custom format
import * as React from "react"
import { InputDuration } from "@/components/ui/input-duration"
export function DurationFormatField() {
const [value, setValue] = React.useState<number | null>(null)
return (
<InputDuration
format="dd'd' HH'h' mm'm'"
value={value}
onValueChange={setValue}
/>
)
}Colon-separated time
import * as React from "react"
import { InputDuration } from "@/components/ui/input-duration"
export function ColonDurationField() {
const [value, setValue] = React.useState<number | null>(null)
return (
<InputDuration format="HH:mm:ss" value={value} onValueChange={setValue} />
)
}Examples
Default format (HH'h' mm'm' ss's')
Committed seconds (onValueChange): —
Hidden input (onChange, same as commit): —
Days, hours, and minutes
Committed (seconds): —
Colon-separated (HH:mm:ss)
Colons as separators only — no unit letters. Same token rules as Input DateTime.
Years, months, days, hours, and minutes
Format includes years, months, and days plus hours and minutes so total seconds can be split across all units. Committed value is total seconds.
Committed (seconds): —
Size variants
Use size so the field aligns with InputGroup and Input dimensions. Segment padding and text scale with the group.
xs
sm
default
lg