Components · Forms
DurationInput
Free-text duration field ("1h 30m", "90m", "1.5h") with a minutes value and arrow-key steps.
Estimate
Type 1h 30m, 90m or 1.5h. Arrow keys step by 15 minutes.
import { useState } from 'react';
import { Field, FieldDescription, FieldLabel } from '@rookcom/monarch-react';
import { DurationInput } from '@rookcom/monarch-react/scheduling';
function EstimateDemo() {
const [minutes, setMinutes] = useState<number | null>(90);
return (
<Field style={{ width: '100%', maxWidth: 240 }}>
<FieldLabel>Estimate</FieldLabel>
<DurationInput value={minutes} onValueChange={setMinutes} />
<FieldDescription>Type 1h 30m, 90m or 1.5h. Arrow keys step by 15 minutes.</FieldDescription>
</Field>
);
}
<EstimateDemo />Usage examples
Limits, days and small
import { DurationInput } from '@rookcom/monarch-react/scheduling';
<div style={{ display: 'grid', gap: 12, width: '100%', maxWidth: 240 }}>
<DurationInput
aria-label="Buffer"
size="sm"
min={5}
max={120}
step={5}
defaultValue={10}
/>
<DurationInput aria-label="Response target" largestUnit="d" defaultValue={1680} />
</div>Purpose
Enters a length of time the way people say it and stores whole minutes: estimates, time logged, meeting lengths, SLA targets. Import from @rookcom/monarch-react/scheduling.
Use when
- A duration in minutes or hours: estimates, time tracking, buffers, response targets.
Avoid when
- A time of day: use a time picker.
- A date range: use DateRangePicker.
- A handful of fixed lengths (15, 30, 60 minutes): use SegmentedControl or Select.
Design principles
- Accept what people type: "90", "90m", "1h 30m", "1h30", "1.5h", "1,5h", "1:30", "2 hours", "1d 4h".
- Show it back one way ("1h 30m") so the value is unambiguous.
- Never save a guess: unreadable or out-of-range text stays, is flagged and explained.
- The value is always whole minutes; formatting and parsing are replaceable for other conventions.
API reference
import { DurationInput, formatDuration, parseDuration } from '@rookcom/monarch-react/scheduling';| Export | Kind | Rendering | Root class |
|---|---|---|---|
DurationInput | component | client | .mn-duration-input- |
formatDuration | utility | client | – |
parseDuration | utility | client | – |
Renders .mn-duration-input wrapping the input and its message. The ref and native input props go to the input; className and style go to the wrapper.
Helpers: parseDuration(text, { minutesPerDay }) returns minutes, null (empty) or NaN; formatDuration(minutes, { largestUnit, minutesPerDay }).
Generated from the TypeScript declarations of packages/react/src/components/forms/duration-input.
Properties
onValueChange fires on blur, Enter and arrow steps, never on every keystroke.
DurationInput
Free-text duration field with a minutes value. Reads "1h 30m", "90m", "1.5h" or "1:30", shows the value back as "1h 30m", and steps with the arrow keys. Unreadable or out-of-range text is kept, flagged and explained, never saved. The input is a `spinbutton` with a spoken value.
| Prop | Type | Description |
|---|---|---|
children | ReactNode | |
className | string | |
defaultValue | number | null | |
disabled | boolean | |
format | ((minutes: number) => string) | Custom display format. Default `formatDuration`. |
id | string | |
invalid | boolean | |
largestUnit | "h" | "d" | Largest unit when formatting: `h` (default) or `d`. |
max | number | Largest allowed value in minutes. |
messages | Partial<DurationInputMessages> | Interface text, for translation. |
min | number | Smallest allowed value in minutes. Default 0. |
minutesPerDay | number | Minutes in a "d" when parsing and formatting. Default 1440. |
onValueChange | ((minutes: number | null) => void) | Called with minutes (or `null` when cleared) on blur, Enter or an arrow-key step; never while typing. |
parse | ((text: string) => number | null) | Custom parser: minutes, `null` for empty, or `NaN` when unreadable. Default `parseDuration`. |
size | "sm" | "md" | "lg" | |
step | number | Minutes added or removed by ArrowUp / ArrowDown. Default 15. PageUp / PageDown step by 60. |
value | number | null | Duration in whole minutes (controlled). `null` is empty. |
Variants
| Prop | Values | Description |
|---|---|---|
size | 'sm''md''lg' | Input height from --mn-control-*. |
largestUnit | 'h''d' | "26h" or "1d 2h". Use minutesPerDay={480} for 8-hour work days. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Unreadable | aria-invalid="true" | "Use a duration like 1h 30m, 90m or 1.5h." under the field; the text is kept. |
| Out of range | aria-invalid="true" | "Enter between 15m and 2h." from min and max. |
| Empty | – | The value is null; aria-valuenow is removed. |
| Disabled / read-only | disabled, readonly | Keys do nothing. |
Interaction behaviour
- Typing never changes the value; blur or Enter reads the text, saves minutes and reformats it.
- Clearing the text saves
null. - Escape restores the last saved value and clears the message.
- Arrow steps snap to the step grid (50m + 15 → 1h) and clamp to
minandmax.
Keyboard support
| Keys | Action |
|---|---|
| ArrowUp / ArrowDown | Adds or removes step minutes (default 15). |
| PageUp / PageDown | Moves to the next or previous whole hour. |
| Home / End | Sets min / max (End only with max). |
| Enter | Reads and saves the text. |
| Escape | Restores the saved value. |
Accessibility
- Role
- spinbutton
- Accessible name
- A FieldLabel or
aria-label.
aria-valuenowis the minutes,aria-valuetexta spoken form ("1 hour 30 minutes"),aria-valuemin/aria-valuemaxthe limits.- The error message is linked with
aria-describedbyand announced as an alert. - Field descriptions and errors are wired as for Input.
Responsive behaviour
- Fills its container; set a width on the Field. Numbers are tabular so values do not jitter.
Theme support
- Input tokens; the message uses
--mn-color-danger-strongin every theme.
Do and don't
Do
- Say in a FieldDescription which formats work when the field is new to people.
- Set
minandmaxwhen the product has limits, so the message can say them.
Don't
- Do not use a number input with a separate unit select for durations people type.
- Do not save on every keystroke; wait for
onValueChange.
Related components
Dependencies
| Kind | Depends on |
|---|---|
| npm packages | react |
| Monarch components | Field |
Uses the .mn-input styles from text-field.
Testing coverage
- Parses 13 input shapes, rejects unreadable text, honours minutesPerDay, formats h/m and days.
- Commits on blur with spoken value; unreadable text is kept, flagged, explained and Escape restores.
- Range message; arrow, page, Home and End steps with snapping and clamping.
- Clearing to null, controlled updates, custom format and read-only.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/duration-input; Cross-browser axe: /components/duration-input |
Tests: packages/react/src/components/forms/duration-input