Forms
Time field
A time of day: typed hour then minute, or chosen from a list. Written the way your reader’s clock writes it.
Installation
npx sinaui add time-field time-range-fieldCopy src/lib/ui/TimeField.svelte, floating.ts, Icon.svelte and tokens.css, then install its date library:
pnpm add @internationalized/dateThe value is a Time from that library: hours, minutes and seconds, no date.
For a start and an end, also copy TimeRangeField.svelte; its value is { start, end }.
Examples
Basic
Type 930 and a for 9:30 AM; each part moves on to the next when it’s full. Arrow keys step through values.
Stored as 09:30
<script lang="ts">
import { Time } from '@internationalized/date';
import TimeField from '#lib/ui/TimeField.svelte';
let lecture = $state<Time | undefined>(new Time(9, 30));
</script>
<div class="stack">
<TimeField label="Lecture starts" bind:value={lecture} />
<p>{lecture ? `Stored as ${lecture.toString().slice(0, 5)}` : 'Not set'}</p>
</div>
<style>
.stack {
display: grid;
gap: 0.75rem;
inline-size: min(100%, 12rem);
}
p {
margin: 0;
color: var(--ui-muted);
font-size: 0.875rem;
font-variant-numeric: tabular-nums;
}
</style>A start and an end
TimeRangeField: one box for both times. Choose a start from the clock’s list and the end’s list follows, showing how long each option runs. Moving the start carries the end with it.
Stored as 08:00–17:30
<script lang="ts">
import { Time } from '@internationalized/date';
import TimeRangeField, { type TimeRange } from '#lib/ui/TimeRangeField.svelte';
let hours = $state<TimeRange | undefined>({ start: new Time(8), end: new Time(17, 30) });
</script>
<div class="stack">
<TimeRangeField label="Library hours" bind:value={hours} minuteStep={15} />
<p>
{#if hours}Stored as {hours.start.toString().slice(0, 5)}–{hours.end.toString().slice(0, 5)}
{:else}Not set{/if}
</p>
</div>
<style>
.stack {
display: grid;
gap: 0.75rem;
}
p {
margin: 0;
color: var(--ui-muted);
font-size: 0.875rem;
font-variant-numeric: tabular-nums;
}
</style>24 hours, with seconds
As recorded at the Raqqa observatory.
<script lang="ts">
import { Time } from '@internationalized/date';
import TimeField from '#lib/ui/TimeField.svelte';
let sighting = $state<Time | undefined>(new Time(21, 4, 36));
</script>
<div class="stack">
<TimeField
label="Star sighted at"
bind:value={sighting}
granularity="second"
hourCycle={24}
hint="As recorded at the Raqqa observatory."
/>
</div>
<style>
.stack {
inline-size: min(100%, 14rem);
}
</style>In Arabic
The order, the digits and the words for morning and evening all follow the locale.
<script lang="ts">
import { Time } from '@internationalized/date';
import TimeField from '#lib/ui/TimeField.svelte';
let lesson = $state<Time | undefined>(new Time(18, 12));
</script>
<div class="stack" dir="rtl" lang="ar">
<TimeField label="موعد الدرس" bind:value={lesson} locale="ar-EG" />
</div>
<style>
.stack {
inline-size: min(100%, 12rem);
}
</style>Props
| Prop | Type | Default |
|---|---|---|
label | string (required) | |
value | Time, bindable | |
locale | string | 'en' |
granularity | 'minute' | 'second' | 'minute' |
hourCycle | 12 | 24: overrides the locale | |
listStep | number: minutes between times in the list | 30, or minuteStep if larger |
minuteStep | number | 1 |
name | string: a hidden input with HH:MM | |
hint, error | string | |
disabled | boolean | false |
Keyboard
| Key | Does |
|---|---|
| 0–9 | Fill the part; moves on when no other digit could follow |
| A / P | Morning or afternoon |
| Arrow Up / Down | Next or previous value (wraps around) |
| Page Up / Page Down | Hours by 2, minutes by 15 |
| Home / End | Lowest or highest value |
| Arrow Left / Right | Previous or next part |
| Alt + Arrow Down | Open the list of times |
| Backspace | Remove a digit; on an empty part, go back one |
Accessibility
- Each part is a spin button named with the field’s label (“Lecture starts, hour”) and read with its value, or “Empty”.
- On phones, the number pad opens for hours and minutes.
- The clock button opens a list on the chosen time (or the next one after it); arrow keys move, Enter chooses, Escape returns to the field.
- The whole field has the focus ring; the part being typed into is filled in.