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

Open the live example: Input editors: text, textarea, OTP, duration, multi-select (Inputs)

<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

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