Date Field
Dates & Timeimport { DateField } from "bits-ui" Segmented date
Locale-aware month, day, and year segments with native form participation
<script lang="ts">
import { DateField } from 'bits-ui';
import {
CalendarDate,
DateFormatter,
getLocalTimeZone,
today,
type DateValue
} from '@internationalized/date';
const timeZone = getLocalTimeZone();
const current = today(timeZone);
const initial = new CalendarDate(current.year, current.month, current.day).add({ days: 7 });
const formatter = new DateFormatter('en-US', { dateStyle: 'long' });
let value = $state<DateValue | undefined>(initial);
let savedLabel = $state<string>();
const formattedValue = $derived(
value ? formatter.format(value.toDate(timeZone)) : 'No arrival date selected'
);
function handleSubmit(event: SubmitEvent) {
event.preventDefault();
savedLabel = formattedValue;
}
</script>
<form class="grid gap-4" onsubmit={handleSubmit}>
<DateField.Root bind:value placeholder={current}>
<div class="grid gap-2">
<DateField.Label class="text-sm font-medium text-ink-50">Arrival date</DateField.Label>
<DateField.Input
name="arrivalDate"
class="input flex h-11 select-none items-center py-0 focus-within:border-brand-500"
>
{#snippet children({ segments })}
{#each segments as { part, value: segmentValue }, index (part + index)}
{#if part === 'literal'}
<DateField.Segment {part} class="px-0.5 text-ink-600">
{segmentValue}
</DateField.Segment>
{:else}
<DateField.Segment
{part}
class="rounded-md px-1.5 py-1 font-mono text-sm tabular-nums text-ink-50 outline-none hover:bg-ink-800 focus:bg-ink-800 focus:ring-2 focus:ring-brand-500 aria-[valuetext=Empty]:text-ink-600"
>
{segmentValue}
</DateField.Segment>
{/if}
{/each}
{/snippet}
</DateField.Input>
<p class="muted text-xs">Use the arrow keys to adjust the focused segment.</p>
</div>
</DateField.Root>
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-ink-800 bg-ink-950 px-3 py-2">
<output class="text-sm font-medium text-ink-200">{formattedValue}</output>
<button class="btn btn-primary btn-sm" type="submit">Save arrival</button>
</div>
{#if savedLabel}
<p class="text-xs text-brand-400" role="status">
Saved {savedLabel}; the named field also participates in native form submission.
</p>
{/if}
</form>
Custom validation
validate returns an error message while errorMessageId connects it to the field
Current value
Saturday, September 5, 2026
Dispatch dates must fall on a weekday.
<script lang="ts">
import { DateField } from 'bits-ui';
import {
CalendarDate,
DateFormatter,
getDayOfWeek,
getLocalTimeZone,
today,
type DateValue
} from '@internationalized/date';
import { AlertCircle, Check } from '@lucide/svelte';
const timeZone = getLocalTimeZone();
const current = today(timeZone);
const daysUntilSaturday = (6 - getDayOfWeek(current, 'en-US') + 7) % 7;
const weekend = new CalendarDate(current.year, current.month, current.day).add({
days: daysUntilSaturday
});
const nextBusinessDay: DateValue = weekend.add({ days: 2 });
const formatter = new DateFormatter('en-US', { dateStyle: 'full' });
let value = $state<DateValue | undefined>(weekend);
function validate(date: DateValue): string | undefined {
const weekday = getDayOfWeek(date, 'en-US');
if (weekday === 0 || weekday === 6) {
return 'Dispatch dates must fall on a weekday.';
}
}
function formatDate(date: DateValue | undefined) {
return date ? formatter.format(date.toDate(timeZone)) : 'No date selected';
}
const errorMessage = $derived(value ? validate(value) : undefined);
</script>
<div class="grid gap-4">
<DateField.Root
bind:value
placeholder={current}
minValue={current}
{validate}
errorMessageId="dispatch-date-error"
>
<div class="grid gap-2">
<DateField.Label class="text-sm font-medium text-ink-50 data-[invalid]:text-accent-500">
Dispatch date
</DateField.Label>
<DateField.Input
name="dispatchDate"
class="input flex h-11 select-none items-center py-0 focus-within:border-brand-500 data-[invalid]:border-accent-500"
>
{#snippet children({ segments })}
{#each segments as { part, value: segmentValue }, index (part + index)}
{#if part === 'literal'}
<DateField.Segment {part} class="px-0.5 text-ink-600">
{segmentValue}
</DateField.Segment>
{:else}
<DateField.Segment
{part}
class="rounded-md px-1.5 py-1 font-mono text-sm tabular-nums text-ink-50 outline-none hover:bg-ink-800 focus:bg-ink-800 focus:ring-2 focus:ring-brand-500 aria-[valuetext=Empty]:text-ink-600 data-[invalid]:text-accent-500"
>
{segmentValue}
</DateField.Segment>
{/if}
{/each}
{/snippet}
</DateField.Input>
</div>
</DateField.Root>
<div class="card flex flex-wrap items-center justify-between gap-3 p-3">
<div>
<p class="muted text-xs">Current value</p>
<p class="mt-0.5 text-sm font-medium text-ink-50">{formatDate(value)}</p>
</div>
<button class="btn btn-sm" onclick={() => (value = nextBusinessDay)}>
<Check class="size-3.5" aria-hidden="true" />
Use next weekday
</button>
</div>
{#if errorMessage}
<p id="dispatch-date-error" class="flex items-center gap-2 text-xs text-accent-500" role="alert">
<AlertCircle class="size-4" aria-hidden="true" />
{errorMessage}
</p>
{:else}
<p id="dispatch-date-error" class="text-xs text-brand-400" role="status">
This date passes the custom validation rule.
</p>
{/if}
</div>
Date and time
granularity="minute" adds hour and minute segments; readonlySegments locks the year
Design review
The year segment is visible but read-only.
<script lang="ts">
import { DateField } from 'bits-ui';
import {
CalendarDateTime,
DateFormatter,
getLocalTimeZone,
today,
type DateValue
} from '@internationalized/date';
import { Clock3 } from '@lucide/svelte';
const timeZone = getLocalTimeZone();
const current = today(timeZone);
const initial = new CalendarDateTime(current.year, current.month, current.day, 9, 30);
const formatter = new DateFormatter('en-US', {
dateStyle: 'medium',
timeStyle: 'short'
});
let value = $state<DateValue | undefined>(initial);
const formattedValue = $derived(
value ? formatter.format(value.toDate(timeZone)) : 'No session scheduled'
);
function setTime(hour: number, minute: number) {
value = initial.set({ hour, minute });
}
</script>
<div class="grid gap-4">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<p class="text-sm font-medium text-ink-50">Design review</p>
<p class="muted text-xs">The year segment is visible but read-only.</p>
</div>
<Clock3 class="size-5 text-brand-400" aria-hidden="true" />
</div>
<DateField.Root
bind:value
placeholder={initial}
granularity="minute"
hourCycle={24}
readonlySegments={['year']}
>
<div class="grid gap-2">
<DateField.Label class="text-sm font-medium text-ink-50">Session date and time</DateField.Label>
<DateField.Input
name="sessionStartsAt"
class="input flex min-h-11 flex-wrap items-center py-1 focus-within:border-brand-500"
>
{#snippet children({ segments })}
{#each segments as { part, value: segmentValue }, index (part + index)}
{#if part === 'literal'}
<DateField.Segment {part} class="px-0.5 text-ink-600">
{segmentValue}
</DateField.Segment>
{:else}
<DateField.Segment
{part}
class="rounded-md px-1.5 py-1 font-mono text-sm tabular-nums text-ink-50 outline-none hover:bg-ink-800 focus:bg-ink-800 focus:ring-2 focus:ring-brand-500 aria-[valuetext=Empty]:text-ink-600 data-[readonly]:text-ink-600"
>
{segmentValue}
</DateField.Segment>
{/if}
{/each}
{/snippet}
</DateField.Input>
</div>
</DateField.Root>
<div class="card grid gap-3 p-3">
<output class="text-sm font-medium text-ink-50">{formattedValue}</output>
<div class="flex flex-wrap gap-2">
<button class="btn btn-sm" onclick={() => setTime(9, 30)}>09:30</button>
<button class="btn btn-sm" onclick={() => setTime(14, 0)}>14:00</button>
<button class="btn btn-sm" onclick={() => setTime(16, 30)}>16:30</button>
</div>
</div>
</div>
Date Field stores immutable DateValue objects from @internationalized/date. A CalendarDate defaults to day granularity; a date-time value defaults to minute granularity. The placeholder is not empty-field text: it seeds segment cycling and can determine the resulting value type, while value is the user's completed selection.
Every rendered segment must receive its part, including locale-specific literals. Setting name on DateField.Input adds the hidden input used by native form submission; errorMessageId associates custom validation text with the segmented field.