Date Picker
Dates & TimeLocal guide ↗
import { DatePicker } from "bits-ui" Field and calendar
A segmented field and popover calendar share one DateValue
Release date
09142026
<script lang="ts">
import { DatePicker } from 'bits-ui';
import {
CalendarDate,
DateFormatter,
getLocalTimeZone,
today,
type DateValue
} from '@internationalized/date';
import { CalendarDays, ChevronLeft, ChevronRight } from '@lucide/svelte';
const timeZone = getLocalTimeZone();
const current = today(timeZone);
const placeholder = new CalendarDate(current.year, current.month, 1);
const formatter = new DateFormatter('en-US', { dateStyle: 'full' });
let value = $state<DateValue | undefined>(current.add({ days: 14 }));
const formattedValue = $derived(
value ? formatter.format(value.toDate(timeZone)) : 'No release date selected'
);
</script>
<DatePicker.Root bind:value {placeholder}>
<div class="grid gap-2">
<DatePicker.Label class="text-sm font-medium text-ink-50">Release date</DatePicker.Label>
<div class="flex items-stretch">
<DatePicker.Input
name="releaseDate"
class="input flex h-11 min-w-0 flex-1 select-none items-center rounded-r-none border-r-0 py-0 focus-within:border-brand-500"
>
{#snippet children({ segments })}
{#each segments as { part, value: segmentValue }, index (part + index)}
{#if part === 'literal'}
<DatePicker.Segment {part} class="px-0.5 text-ink-600">
{segmentValue}
</DatePicker.Segment>
{:else}
<DatePicker.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}
</DatePicker.Segment>
{/if}
{/each}
{/snippet}
</DatePicker.Input>
<DatePicker.Trigger
class="btn rounded-l-none px-3"
aria-label="Open release date calendar"
>
<CalendarDays class="size-4" aria-hidden="true" />
</DatePicker.Trigger>
</div>
<output class="muted text-xs">Selected: {formattedValue}</output>
</div>
<DatePicker.Portal>
<DatePicker.Content
sideOffset={8}
align="end"
class="panel z-50 w-80 max-w-[calc(100vw-2rem)] animate-pop-in p-4"
>
<DatePicker.Calendar class="w-full">
{#snippet children({ months, weekdays })}
<DatePicker.Header class="mb-4 flex items-center justify-between">
<DatePicker.PrevButton class="btn btn-ghost btn-sm size-9 p-0" aria-label="Previous month">
<ChevronLeft class="size-4" aria-hidden="true" />
</DatePicker.PrevButton>
<DatePicker.Heading class="text-sm font-semibold text-ink-50" />
<DatePicker.NextButton class="btn btn-ghost btn-sm size-9 p-0" aria-label="Next month">
<ChevronRight class="size-4" aria-hidden="true" />
</DatePicker.NextButton>
</DatePicker.Header>
{#each months as month (month.value.toString())}
<DatePicker.Grid class="w-full table-fixed border-collapse">
<DatePicker.GridHead>
<DatePicker.GridRow>
{#each weekdays as day}
<DatePicker.HeadCell class="h-7 text-center text-[0.65rem] font-medium text-ink-600">
{day}
</DatePicker.HeadCell>
{/each}
</DatePicker.GridRow>
</DatePicker.GridHead>
<DatePicker.GridBody>
{#each month.weeks as weekDates, weekIndex (weekIndex)}
<DatePicker.GridRow>
{#each weekDates as date (date.toString())}
<DatePicker.Cell {date} month={month.value} class="p-0 text-center">
<DatePicker.Day
class="inline-flex size-9 items-center justify-center rounded-lg text-sm text-ink-200 outline-none transition hover:bg-ink-800 focus-visible:ring-2 focus-visible:ring-brand-500 data-[disabled]:pointer-events-none data-[disabled]:opacity-30 data-[outside-month]:text-ink-700 data-[selected]:bg-brand-500 data-[selected]:font-semibold data-[selected]:text-ink-950 data-[today]:ring-1 data-[today]:ring-brand-400"
/>
</DatePicker.Cell>
{/each}
</DatePicker.GridRow>
{/each}
</DatePicker.GridBody>
</DatePicker.Grid>
{/each}
{/snippet}
</DatePicker.Calendar>
</DatePicker.Content>
</DatePicker.Portal>
</DatePicker.Root>
Controlled review
bind:open exposes popover state; closeOnDateSelect=false keeps the calendar open for review
Security review
Choose a date, inspect it, then close explicitly.
Review date
09052026
<script lang="ts">
import { DatePicker } from 'bits-ui';
import {
CalendarDate,
DateFormatter,
getLocalTimeZone,
today,
type DateValue
} from '@internationalized/date';
import { CalendarDays, ChevronLeft, ChevronRight, X } from '@lucide/svelte';
const timeZone = getLocalTimeZone();
const current = today(timeZone);
const placeholder = new CalendarDate(current.year, current.month, 1);
const formatter = new DateFormatter('en-US', { dateStyle: 'long' });
const unavailableDates = new Set([
current.add({ days: 7 }).toString(),
current.add({ days: 12 }).toString()
]);
let value = $state<DateValue | undefined>(current.add({ days: 5 }));
let open = $state(false);
const formattedValue = $derived(
value ? formatter.format(value.toDate(timeZone)) : 'No review date selected'
);
function isDateUnavailable(date: DateValue) {
return unavailableDates.has(date.toString());
}
</script>
<DatePicker.Root
bind:value
bind:open
{placeholder}
minValue={current}
maxValue={current.add({ days: 30 })}
{isDateUnavailable}
closeOnDateSelect={false}
initialFocus
>
<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">Security review</p>
<p class="muted text-xs">Choose a date, inspect it, then close explicitly.</p>
</div>
<span class="chip font-mono">popover: {open ? 'open' : 'closed'}</span>
</div>
<div class="grid gap-2">
<DatePicker.Label class="text-sm font-medium text-ink-50">Review date</DatePicker.Label>
<div class="flex items-stretch">
<DatePicker.Input
name="securityReviewDate"
class="input flex h-11 min-w-0 flex-1 select-none items-center rounded-r-none border-r-0 py-0 focus-within:border-brand-500"
>
{#snippet children({ segments })}
{#each segments as { part, value: segmentValue }, index (part + index)}
{#if part === 'literal'}
<DatePicker.Segment {part} class="px-0.5 text-ink-600">
{segmentValue}
</DatePicker.Segment>
{:else}
<DatePicker.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}
</DatePicker.Segment>
{/if}
{/each}
{/snippet}
</DatePicker.Input>
<DatePicker.Trigger
class="btn rounded-l-none px-3"
aria-label="Open security review calendar"
>
<CalendarDays class="size-4" aria-hidden="true" />
<span class="hidden sm:inline">Edit</span>
</DatePicker.Trigger>
</div>
</div>
<div class="card flex flex-wrap items-center justify-between gap-3 p-3">
<output class="text-sm font-medium text-ink-50">{formattedValue}</output>
<div class="flex gap-2">
<button class="btn btn-ghost btn-sm" type="button" onclick={() => (value = undefined)}>
<X class="size-3.5" aria-hidden="true" />
Clear
</button>
<button class="btn btn-primary btn-sm" type="button" onclick={() => (open = true)}>
Choose date
</button>
</div>
</div>
</div>
<DatePicker.Portal>
<DatePicker.Content
sideOffset={8}
align="end"
class="panel z-50 w-80 max-w-[calc(100vw-2rem)] animate-pop-in p-4"
>
<div class="mb-3 flex items-center justify-between gap-3">
<p class="text-xs font-semibold tracking-wider text-ink-400 uppercase">Available dates</p>
<span class="chip">stays open</span>
</div>
<DatePicker.Calendar class="w-full">
{#snippet children({ months, weekdays })}
<DatePicker.Header class="mb-4 flex items-center justify-between">
<DatePicker.PrevButton class="btn btn-ghost btn-sm size-9 p-0" aria-label="Previous month">
<ChevronLeft class="size-4" aria-hidden="true" />
</DatePicker.PrevButton>
<DatePicker.Heading class="text-sm font-semibold text-ink-50" />
<DatePicker.NextButton class="btn btn-ghost btn-sm size-9 p-0" aria-label="Next month">
<ChevronRight class="size-4" aria-hidden="true" />
</DatePicker.NextButton>
</DatePicker.Header>
{#each months as month (month.value.toString())}
<DatePicker.Grid class="w-full table-fixed border-collapse">
<DatePicker.GridHead>
<DatePicker.GridRow>
{#each weekdays as day}
<DatePicker.HeadCell class="h-7 text-center text-[0.65rem] font-medium text-ink-600">
{day}
</DatePicker.HeadCell>
{/each}
</DatePicker.GridRow>
</DatePicker.GridHead>
<DatePicker.GridBody>
{#each month.weeks as weekDates, weekIndex (weekIndex)}
<DatePicker.GridRow>
{#each weekDates as date (date.toString())}
<DatePicker.Cell {date} month={month.value} class="p-0 text-center">
<DatePicker.Day
class="inline-flex size-9 items-center justify-center rounded-lg text-sm text-ink-200 outline-none transition hover:bg-ink-800 focus-visible:ring-2 focus-visible:ring-brand-500 data-[disabled]:pointer-events-none data-[disabled]:opacity-30 data-[outside-month]:text-ink-700 data-[selected]:bg-brand-500 data-[selected]:font-semibold data-[selected]:text-ink-950 data-[unavailable]:text-accent-500 data-[unavailable]:line-through data-[today]:ring-1 data-[today]:ring-brand-400"
/>
</DatePicker.Cell>
{/each}
</DatePicker.GridRow>
{/each}
</DatePicker.GridBody>
</DatePicker.Grid>
{/each}
{/snippet}
</DatePicker.Calendar>
<div class="mt-3 flex items-center justify-between gap-2 border-t border-ink-800 pt-3">
<span class="muted text-xs">Unavailable dates are struck through.</span>
<button class="btn btn-primary btn-sm" type="button" onclick={() => (open = false)}>Done</button>
</div>
</DatePicker.Content>
</DatePicker.Portal>
</DatePicker.Root>
Date Picker shares one immutable DateValue from @internationalized/date across its field and calendar. placeholder is not the selected value or empty-field text: it seeds segment editing and controls which month is visible when value is empty.
DatePicker.Portal renders content into document.body by default; pass disabled to keep it in place. Content exposes opening and closing data attributes plus positioning variables such as --bits-popover-content-available-height and --bits-popover-content-transform-origin.