SvDurationInput
A duration editor whose value is a number of minutes but which accepts the human forms people actually type.
SvDurationInput stores a plain minute count yet lets users enter 1h 30m,
1:30, or 90 - it parses on blur or Enter and shows a tidy formatted value
when unfocused. That keeps your model numeric (easy to sum, compare, and store)
while the UI stays human. Its label / hint / error chrome comes from
SvField. As a grid cell editor: Enter commits, Escape cancels.
Related: SvNumberInput · SvMaskedInput · Inputs overview
Installation
Add it with the CLI - this drops a ready-to-edit SvDurationInput starter into your app:
Prefer to see it first? npx @svgrid/ui try duration-input opens it in a throwaway sandbox - no project needed.
Or install the package and import it directly. SvDurationInput ships free in
@svgrid/grid and is part of the grid's editor kit - the same control SvGrid mounts when you edit a matching cell:
The examples on this page import from @svgrid/grid:
<script lang="ts">
import { SvDurationInput } from '@svgrid/grid'
// The bound value behind each example below.
let runtime = $state(0)
let estimate = $state(0)
</script>
import { SvDurationInput } from '@svgrid/grid'
Example
<script lang="ts">
import { SvDurationInput } from '@svgrid/grid'
let minutes = $state<number | null>(90)
</script>
<SvDurationInput label="Task estimate" bind:value={minutes} style="units" />
<p>Stored as {minutes} minutes</p>
Props
SvDurationInput extends the shared SvEditorProps (disabled, readonly,
required, invalid, error, label, hint, size, dir, name, id,
ariaLabel) and adds:
| Prop | Type | Default | Description |
|---|---|---|---|
value |
number | null |
null |
Value in minutes. Bindable with bind:value. |
onChange |
(minutes: number | null) => void |
- | Fires with the parsed minutes on commit. |
onCommit |
(minutes: number | null) => void |
- | Fires on Enter. |
onCancel |
() => void |
- | Fires on Escape. |
style |
colon | units |
colon |
Display when unfocused: 1:30 vs 1h 30m. |
placeholder |
string |
e.g. 1h 30m |
Empty-state hint text. |
autofocus |
boolean |
false |
Focus + select on mount (used as a cell editor). |
clearable |
boolean |
false |
Show a clear (x) button when there is a value. |
leading |
Snippet |
- | Leading adornment (icon) inside the field. |
trailing |
Snippet |
- | Trailing adornment (icon) inside the field. |
block |
boolean |
false |
Stretch to the container width. |
width |
number |
160 |
Control width in px (ignored when block). |
The box, size, invalid state and focus ring are owned by SvField's
shared frame chrome.
Examples
Colon vs units display
Pick the resting format that fits your domain - colon reads like a clock,
units reads like an estimate:
<SvDurationInput style="colon" bind:value={runtime} />
<SvDurationInput style="units" bind:value={estimate} />
Flexible typing
The field accepts several shapes and normalizes them on blur, so 90, 1:30,
and 1h 30m all resolve to the same 90 minutes. Empty input yields null.
Timesheet total from a numeric model
Because each field stores plain minutes, a running total is just arithmetic -
no parsing of 1h 30m strings on your side:
<script lang="ts">
import { SvDurationInput } from '@svgrid/grid'
let mon = $state<number | null>(480)
let tue = $state<number | null>(510)
const week = $derived((mon ?? 0) + (tue ?? 0))
const hrs = $derived(Math.floor(week / 60))
const mins = $derived(week % 60)
</script>
<SvDurationInput label="Monday" style="units" bind:value={mon} />
<SvDurationInput label="Tuesday" style="units" bind:value={tue} />
<p>Week total: {hrs}h {mins}m ({week} min)</p>
Tip: parsing runs on blur and Enter, so bind to value (the committed minutes)
for calculations rather than trying to read the mid-edit text.
Accessibility
- Renders a native
<input>withinputmode="numeric"for a numeric keypad. label,hint, anderrorare wired viafor/idandaria-describedbyby SvField;requiredandinvalidadd the matching ARIA.- Parsing happens on blur and
Enter, so assistive tech reads the committed, formatted value rather than mid-edit text.
Sizes
Every control takes the same three sizes, so a dense toolbar and a roomy form can share components.
<script lang="ts">
import { SvDurationInput } from '@svgrid/grid'
let minutes = $state(1)
</script>
<SvDurationInput bind:value={minutes} size="sm" />
<SvDurationInput bind:value={minutes} size="md" />
<SvDurationInput bind:value={minutes} size="lg" />
In a form
The shared field props behave the same on every editor: label names it, hint explains it, and error plus invalid mark it - which is why a validated form does not need per-component handling.
<script lang="ts">
import { SvDurationInput } from '@svgrid/grid'
let minutes = $state(1)
</script>
<SvDurationInput
bind:value={minutes}
label="Label"
hint="A short hint"
required
/>
<SvDurationInput
bind:value={minutes}
label="Label"
error="Something is wrong"
invalid
/>
See also
- Inputs overview - the whole input family at a glance.
- SvNumberInput - plain numeric entry with spinners.
- SvMaskedInput - fixed-pattern text entry.