Inputs

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

KeyBehavior
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.
blurLeaving a segment commits with the same normalization as .

Custom props

PropTypeDescription
formatstringPattern for segments and static text. Default: HH'h' mm'm' ss's'. Use single-quoted literals for static text; see Overview.
valuenumber | nullControlled total duration in seconds, or null for empty.
onValueChange(seconds: number | null) => voidFires 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-duration

This 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')

000000

Committed seconds (onValueChange):

Hidden input (onChange, same as commit):

Days, hours, and minutes

010230

Committed (seconds):

Colon-separated (HH:mm:ss)

000000

Colons as separators only — no unit letters. Same token rules as Input DateTime.

Years, months, days, hours, and minutes

0000010230

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

010101

sm

010101

default

010101

lg

010101