Behaviour
Typing and committing
- Valid input updates
v-modelwhile you type. Invalid input leaves it unchanged. - Clearing the field sets
v-modeltonull. This also happens withrequired, which additionally reports anemptyerror. - On blur and Enter, valid text is rewritten to a normalized form:
189hbecomes7d 21h. - If
v-modelchanges from outside, the text is reformatted.
When errors appear
When an error is shown:
- The input gets
aria-invalid="true"anddata-error="<code>". - The root gets
data-invalid. - A message appears below the field, linked through
aria-describedby. - The
errorevent fires with the code, and again withnullonce the error clears.
validateOn controls when that happens:
validateOn | Behaviour |
|---|---|
'eager' (default) | Not while you're still typing. The error appears on blur or Enter. While it's shown, it updates live, so fixing the input clears it right away. |
'blur' | The shown error only changes on blur or Enter. Stepping, reverting and outside changes to v-model also clear it. |
'input' | The error updates on every keystroke. |
Whatever the mode, calling the exposed validate() shows the current error, e.g. on submit.
Preview
With preview, the normalized value appears inside the field as you type (= 1h 30min), but only when it differs from what you typed. It's also announced to screen readers through a polite live region, after a 600 ms pause in typing so not every keystroke is read out.
preview only works with the built-in field. With as, use the preview slot prop of the renderless slot instead.
Custom preview
formatPreview renders the preview yourself. It gets the duration in seconds and a context with the default preview (normalized), the typed text, the model value and the display locale:
<DurationInput
v-model="minutes"
preview
:format-preview="(seconds, { value }) => `≈ ${(seconds / 3600).toFixed(1)} hours (${value} min)`"
/>Its output is shown and announced as is, without the leading = . The preview still only appears while the text differs from its normalized form, so it goes away on blur. Return null (or an empty string) to hide it for a value.
formatPreview changes the preview slot prop and the preview of useDurationInput too. It only affects the preview: the text is still normalized with displayStyle and displayUnits on blur.
Keyboard
| Key | Action |
|---|---|
| ↑ / ↓ | Step by step (default 15min), snapping to multiples: 1h07 → 1h15 / 1h |
| Shift + ↑ / ↓ | Step by 1 hour, snapping to whole hours |
| PgUp / PgDn | Step by 1 day (as configured in customUnits), snapping to whole days |
| Enter | Normalize and show errors. The form still submits, unless a preset is highlighted in the open menu, in which case that preset is picked |
| Esc | Close the presets menu, or revert to the last committed value. When reverting, the event only stops propagating if something was reverted, so closing a dialog still works |
| Alt + ↓ | Open the presets menu |
Details:
- Steps stay within
min/maxand never go below zero. - If the text is invalid, stepping starts from the last valid value. From an empty field, ↑ gives one step and ↓ gives zero.
:step="false"turns all stepping off, including Shift and PgUp/PgDn.- Keys pressed with Ctrl, Cmd or Alt are left alone (apart from Alt+↓), so browser shortcuts keep working.
Snapping and clamping
snapToSteprounds to the neareststepon blur and Enter, then keeps the result withinmin/max.clampkeeps values withinmin/maxinstead of reportingout_of_range. It applies while typing and on commit.
Presets
presets shows suggestions in a menu below the field. The input is an ARIA combobox.
- Opens on focus or click while the field is empty, on Alt+↓, and while typing, filtered by label.
- While it's open: the arrow keys move through the options, Enter picks the highlighted one, and Esc closes it. Tab and blur close it too.
<DurationInput v-model="minutes" :presets="['15m', '30m', '1h', { label: 'Half a day', value: '4h' }]" />Each entry is one of:
- duration text,
- a number in the model's unit,
{ label, value }.
Entries that can't be parsed are dropped. Style the menu through ui.menu and ui.option; the highlighted option has data-active.
The built-in menu is not rendered with as. To show presets with your own component, use the renderless slot, which gets presets and selectPreset.