Skip to content

DurationField

Terminal window
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/duration-field.json
See it liveFilled, empty, and inside a Field with a hint.→

The alternative is two number boxes. Labelled “hours” and “minutes”, they mean two tab stops, two validations, and a reader who has to divide 90 minutes in their head before typing. Here they write it the way they say it.

<DurationField value={minutes} onValueChange={setMinutes} />

The value is minutes — a plain number, not a string and not a Duration object. A field whose value has to be parsed by its caller has moved the problem rather than solved it.

Loose going in, strict coming out. Everything below means ninety minutes, and all of them are written back as 1h 30m:

Typed Means
1h 30m, 1h30m the canonical spelling, spaced or not
90, 90m a bare number is minutes
1.5h, 1,5h a decimal, with either separator
1:30 the clock spelling

That asymmetry is the design: being strict on input means rejecting people, being loose on output means the column of values never lines up.

What it will not do is guess. 1h banana is refused rather than read as an hour — a typo that parses is a value nobody questions afterwards. When what was typed cannot be read, the box is put back to the value the form actually holds rather than left saying something untrue.

Empty is null. No estimate is not an estimate of nothing.

It does not reformat under the cursor. Typing 1h 3 leaves 1h 3 alone until you leave the field or press Enter; a field that reformats on every keystroke fights the person using it.

parseDuration and formatDuration are exported beside the component, for a product that has to read or write the same spellings elsewhere.

Props

Read from the types in ui/duration-field.tsx, the file a product copies - so the description of each prop is also what an editor shows over it there.

PropTypeDefault
valuerequirednumber | nullMinutes, or null for empty.
onValueChangerequired(value: number | null) => voidTold the new minutes (or null when emptied) on blur and on Enter, not on every key, and only when the parsed value differs from value.
placeholderstringWhat a reader sees before they type. A duration in the canonical spelling is the best hint there is, so this is the product's to give.
disabledbooleanStops the field taking input.
readOnlybooleanShows the value but does not let it be edited.
requiredbooleanMarks the field as required for form validation.
namestringThe field name submitted with a form.
idstringPut on the input, so a label can point at it.
refRef<HTMLInputElement>A ref to the underlying <input>.
aria-labelstringThe accessible name, when there is no visible label.
aria-describedbystringPoints at the hint or error text for the field.
classNamestring

Accessibility

The examples on the stand, through axe 4.14.0 in a browser, in both themes, against WCAG 2.2 A and AA and axe's best practices: 13 rules applied and passed, and none failed. The accessibility report has every item.

History

Each release that changed ui/duration-field.tsx - the file a product copies - newest first.

  1. v0.41.0
    • docs read every props table from the component's types ee3b159
  2. v0.28.0breaking
    • feat density as a property of a region 849d783
  3. v0.17.0
    • feat numbers, scales, and the two fields with no foundation ead7461