Docs
Date Picker
Date Picker
A date picker component with range and presets.
<script lang="ts">
import CalendarIcon from "lucide-svelte/icons/calendar";
import {
DateFormatter,
type DateValue,
getLocalTimeZone
} from "@internationalized/date";
import { cn } from "$lib/utils.js";
import { Button } from "$lib/components/ui/button/index.js";
import { Calendar } from "$lib/components/ui/calendar/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
const df = new DateFormatter("en-US", {
dateStyle: "long"
});
let value: DateValue | undefined = undefined;
</script>
<Popover.Root>
<Popover.Trigger>
{#snippet child({ props })}
<Button
variant="outline"
class={cn(
"w-[240px] justify-start text-left font-normal",
!value && "text-muted-foreground"
)}
{...props}
>
<CalendarIcon />
{value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date"}
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content class="w-auto p-0" align="start">
<Calendar type="single" bind:value />
</Popover.Content>
</Popover.Root>
<script lang="ts">
import CalendarIcon from "lucide-svelte/icons/calendar";
import {
DateFormatter,
type DateValue,
getLocalTimeZone
} from "@internationalized/date";
import { cn } from "$lib/utils.js";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { Calendar } from "$lib/components/ui/calendar/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
const df = new DateFormatter("en-US", {
dateStyle: "long"
});
let value = $state<DateValue | undefined>();
let contentRef = $state<HTMLElement | null>(null);
</script>
<Popover.Root>
<Popover.Trigger
class={cn(
buttonVariants({
variant: "outline",
class: "w-[280px] justify-start text-left font-normal"
}),
!value && "text-muted-foreground"
)}
>
<CalendarIcon />
{value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date"}
</Popover.Trigger>
<Popover.Content bind:ref={contentRef} class="w-auto p-0">
<Calendar type="single" bind:value />
</Popover.Content>
</Popover.Root>
Installation
The Date Picker is built using a composition of the <Popover />
and either the <Calendar />
or <RangeCalendar />
components.
See installations instructions for the Popover, Calendar, and Range Calendar components.
Usage
<script lang="ts">
import CalendarIcon from "lucide-svelte/icons/calendar";
import {
type DateValue,
DateFormatter,
getLocalTimeZone,
} from "@internationalized/date";
import { cn } from "$lib/utils.js";
import { Button } from "$lib/components/ui/button/index.js";
import { Calendar } from "$lib/components/ui/calendar/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
const df = new DateFormatter("en-US", {
dateStyle: "long",
});
let value = $state<DateValue>();
</script>
<Popover.Root>
<Popover.Trigger>
{#snippet child({ props })}
<Button
variant="outline"
class={cn(
"w-[280px] justify-start text-left font-normal",
!value && "text-muted-foreground"
)}
{...props}
>
<CalendarIcon class="mr-2 size-4" />
{value ? df.format(value.toDate(getLocalTimeZone())) : "Select a date"}
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content class="w-auto p-0">
<Calendar bind:value type="single" initialFocus />
</Popover.Content>
</Popover.Root>
Examples
Date Picker
<script lang="ts">
import CalendarIcon from "lucide-svelte/icons/calendar";
import {
DateFormatter,
type DateValue,
getLocalTimeZone
} from "@internationalized/date";
import { cn } from "$lib/utils.js";
import { Button } from "$lib/components/ui/button/index.js";
import { Calendar } from "$lib/components/ui/calendar/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
const df = new DateFormatter("en-US", {
dateStyle: "long"
});
let value: DateValue | undefined = undefined;
</script>
<Popover.Root>
<Popover.Trigger>
{#snippet child({ props })}
<Button
variant="outline"
class={cn(
"w-[240px] justify-start text-left font-normal",
!value && "text-muted-foreground"
)}
{...props}
>
<CalendarIcon />
{value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date"}
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content class="w-auto p-0" align="start">
<Calendar type="single" bind:value />
</Popover.Content>
</Popover.Root>
<script lang="ts">
import CalendarIcon from "lucide-svelte/icons/calendar";
import {
DateFormatter,
type DateValue,
getLocalTimeZone
} from "@internationalized/date";
import { cn } from "$lib/utils.js";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { Calendar } from "$lib/components/ui/calendar/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
const df = new DateFormatter("en-US", {
dateStyle: "long"
});
let value = $state<DateValue | undefined>();
let contentRef = $state<HTMLElement | null>(null);
</script>
<Popover.Root>
<Popover.Trigger
class={cn(
buttonVariants({
variant: "outline",
class: "w-[280px] justify-start text-left font-normal"
}),
!value && "text-muted-foreground"
)}
>
<CalendarIcon />
{value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date"}
</Popover.Trigger>
<Popover.Content bind:ref={contentRef} class="w-auto p-0">
<Calendar type="single" bind:value />
</Popover.Content>
</Popover.Root>
Date Range Picker
<script lang="ts">
import CalendarIcon from "lucide-svelte/icons/calendar";
import type { DateRange } from "bits-ui";
import {
CalendarDate,
DateFormatter,
type DateValue,
getLocalTimeZone
} from "@internationalized/date";
import { cn } from "$lib/utils.js";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { RangeCalendar } from "$lib/components/ui/range-calendar/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
const df = new DateFormatter("en-US", {
dateStyle: "medium"
});
let value = $state<DateRange | undefined>({
start: new CalendarDate(2022, 1, 20),
end: new CalendarDate(2022, 1, 20).add({ days: 20 })
});
let startValue = $state<DateValue | undefined>(undefined);
</script>
<div class="grid gap-2">
<Popover.Root>
<Popover.Trigger
class={cn(
buttonVariants({
variant: "outline",
class: "w-[300px] justify-start text-left font-normal"
}),
!value && "text-muted-foreground"
)}
>
<CalendarIcon />
{#if value && value.start}
{#if value.end}
{df.format(value.start.toDate(getLocalTimeZone()))} - {df.format(
value.end.toDate(getLocalTimeZone())
)}
{:else}
{df.format(value.start.toDate(getLocalTimeZone()))}
{/if}
{:else if startValue}
{df.format(startValue.toDate(getLocalTimeZone()))}
{:else}
Pick a date
{/if}
</Popover.Trigger>
<Popover.Content class="w-auto p-0" align="start">
<RangeCalendar
bind:value
onStartValueChange={(v) => {
startValue = v;
}}
numberOfMonths={2}
/>
</Popover.Content>
</Popover.Root>
</div>
<script lang="ts">
import CalendarIcon from "lucide-svelte/icons/calendar";
import type { DateRange } from "bits-ui";
import {
CalendarDate,
DateFormatter,
type DateValue,
getLocalTimeZone
} from "@internationalized/date";
import { cn } from "$lib/utils.js";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { RangeCalendar } from "$lib/components/ui/range-calendar/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
const df = new DateFormatter("en-US", {
dateStyle: "medium"
});
let value: DateRange = $state({
start: new CalendarDate(2022, 1, 20),
end: new CalendarDate(2022, 1, 20).add({ days: 20 })
});
let startValue: DateValue | undefined = $state(undefined);
</script>
<div class="grid gap-2">
<Popover.Root>
<Popover.Trigger
class={cn(
buttonVariants({ variant: "outline" }),
!value && "text-muted-foreground"
)}
>
<CalendarIcon class="mr-2 size-4" />
{#if value && value.start}
{#if value.end}
{df.format(value.start.toDate(getLocalTimeZone()))} - {df.format(
value.end.toDate(getLocalTimeZone())
)}
{:else}
{df.format(value.start.toDate(getLocalTimeZone()))}
{/if}
{:else if startValue}
{df.format(startValue.toDate(getLocalTimeZone()))}
{:else}
Pick a date
{/if}
</Popover.Trigger>
<Popover.Content class="w-auto p-0" align="start">
<RangeCalendar
bind:value
onStartValueChange={(v) => {
startValue = v;
}}
numberOfMonths={2}
/>
</Popover.Content>
</Popover.Root>
</div>
With Presets
<script lang="ts">
import CalendarIcon from "lucide-svelte/icons/calendar";
import {
DateFormatter,
type DateValue,
getLocalTimeZone,
today
} from "@internationalized/date";
import { cn } from "$lib/utils.js";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { Calendar } from "$lib/components/ui/calendar/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
import * as Select from "$lib/components/ui/select/index.js";
const df = new DateFormatter("en-US", {
dateStyle: "long"
});
let value = $state<DateValue | undefined>();
const valueString = $derived(
value ? df.format(value.toDate(getLocalTimeZone())) : ""
);
const items = [
{ value: 0, label: "Today" },
{ value: 1, label: "Tomorrow" },
{ value: 3, label: "In 3 days" },
{ value: 7, label: "In a week" }
];
</script>
<Popover.Root>
<Popover.Trigger
class={cn(
buttonVariants({
variant: "outline",
class: "w-[240px] justify-start text-left font-normal"
}),
!value && "text-muted-foreground"
)}
>
<CalendarIcon />
{value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date"}
</Popover.Trigger>
<Popover.Content class="flex w-auto flex-col space-y-2 p-2">
<Select.Root
type="single"
bind:value={() => valueString,
(v) => {
if (!v) return;
value = today(getLocalTimeZone()).add({ days: Number.parseInt(v) });
}}
>
<Select.Trigger>
{valueString}
</Select.Trigger>
<Select.Content>
{#each items as item}
<Select.Item value={`${item.value}`}>{item.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
<div class="rounded-md border">
<Calendar type="single" bind:value />
</div>
</Popover.Content>
</Popover.Root>
<script lang="ts">
import CalendarIcon from "lucide-svelte/icons/calendar";
import {
DateFormatter,
type DateValue,
getLocalTimeZone,
today
} from "@internationalized/date";
import { cn } from "$lib/utils.js";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { Calendar } from "$lib/components/ui/calendar/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
import * as Select from "$lib/components/ui/select/index.js";
const df = new DateFormatter("en-US", {
dateStyle: "long"
});
let value: DateValue | undefined = $state();
const valueString = $derived(
value ? df.format(value.toDate(getLocalTimeZone())) : ""
);
const items = [
{ value: 0, label: "Today" },
{ value: 1, label: "Tomorrow" },
{ value: 3, label: "In 3 days" },
{ value: 7, label: "In a week" }
];
</script>
<Popover.Root>
<Popover.Trigger
class={cn(
buttonVariants({
variant: "outline",
class: "w-[280px] justify-start text-left font-normal"
}),
!value && "text-muted-foreground"
)}
>
<CalendarIcon class="mr-2 size-4" />
{value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date"}
</Popover.Trigger>
<Popover.Content class="flex w-auto flex-col space-y-2 p-2">
<Select.Root
type="single"
bind:value={() => valueString,
(v) => {
if (!v) return;
value = today(getLocalTimeZone()).add({ days: Number.parseInt(v) });
}}
>
<Select.Trigger>
{valueString}
</Select.Trigger>
<Select.Content>
{#each items as item}
<Select.Item value={`${item.value}`}>{item.label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
<div class="rounded-md border">
<Calendar type="single" bind:value />
</div>
</Popover.Content>
</Popover.Root>
Form
<script lang="ts" module>
import { z } from "zod";
export const formSchema = z.object({
dob: z
.string()
.refine((v) => v, { message: "A date of birth is required." })
});
export type FormSchema = typeof formSchema;
</script>
<script lang="ts">
import CalendarIcon from "lucide-svelte/icons/calendar";
import {
CalendarDate,
DateFormatter,
type DateValue,
getLocalTimeZone,
parseDate,
today
} from "@internationalized/date";
import type { Infer, SuperValidated } from "sveltekit-superforms";
import SuperDebug, { superForm } from "sveltekit-superforms";
import { zodClient } from "sveltekit-superforms/adapters";
import { toast } from "svelte-sonner";
import { browser } from "$app/environment";
import { page } from "$app/state";
import { cn } from "$lib/utils.js";
import { Button } from "$lib/components/ui/button/index.js";
import { Calendar } from "$lib/components/ui/calendar/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
import * as Form from "$lib/components/ui/form/index.js";
let {
form: data = page.data.datePicker
}: { form: SuperValidated<Infer<FormSchema>> } = $props();
const form = superForm(data, {
validators: zodClient(formSchema),
taintedMessage: null,
onUpdated: ({ form: f }) => {
if (f.valid) {
toast.success(`You submitted ${JSON.stringify(f.data, null, 2)}`);
} else {
toast.error("Please fix the errors in the form.");
}
}
});
const { form: formData, enhance } = form;
const df = new DateFormatter("en-US", {
dateStyle: "long"
});
let value = $state<DateValue | undefined>();
$effect(() => {
value = $formData.dob ? parseDate($formData.dob) : undefined;
});
let placeholder = $state(today(getLocalTimeZone()));
</script>
<form method="POST" action="/?/datePicker" class="space-y-8" use:enhance>
<Form.Field {form} name="dob" class="flex flex-col">
<Form.Control>
{#snippet children({ props })}
<Form.Label>Date of birth</Form.Label>
<Popover.Root>
<Popover.Trigger {...props}>
{#snippet child({ props })}
<Button
variant="outline"
class={cn(
"w-[280px] justify-start pl-4 text-left font-normal",
!value && "text-muted-foreground"
)}
{...props}
>
{value
? df.format(value.toDate(getLocalTimeZone()))
: "Pick a date"}
<CalendarIcon class="ml-auto size-4 opacity-50" />
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content class="w-auto p-0" side="top">
<Calendar
type="single"
{value}
bind:placeholder
minValue={new CalendarDate(1900, 1, 1)}
maxValue={today(getLocalTimeZone())}
calendarLabel="Date of birth"
onValueChange={(v) => {
if (v) {
$formData.dob = v.toString();
} else {
$formData.dob = "";
}
}}
/>
</Popover.Content>
</Popover.Root>
<Form.Description
>Your date of birth is used to calculator your age</Form.Description
>
<Form.FieldErrors />
<input hidden value={$formData.dob} name={props.name} />
{/snippet}
</Form.Control>
</Form.Field>
<Button type="submit">Submit</Button>
{#if browser}
<SuperDebug data={$formData} />
{/if}
</form>
<script lang="ts" module>
import { z } from "zod";
export const formSchema = z.object({
dob: z
.string()
.refine((v) => v, { message: "A date of birth is required." })
});
export type FormSchema = typeof formSchema;
</script>
<script lang="ts">
import CalendarIcon from "lucide-svelte/icons/calendar";
import {
CalendarDate,
DateFormatter,
type DateValue,
getLocalTimeZone,
parseDate,
today
} from "@internationalized/date";
import type { Infer, SuperValidated } from "sveltekit-superforms";
import SuperDebug, { superForm } from "sveltekit-superforms";
import { zodClient } from "sveltekit-superforms/adapters";
import { toast } from "svelte-sonner";
import { browser } from "$app/environment";
import { page } from "$app/state";
import { cn } from "$lib/utils.js";
import {
Button,
buttonVariants
} from "$lib/components/ui/button/index.js";
import { Calendar } from "$lib/components/ui/calendar/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
import * as Form from "$lib/components/ui/form/index.js";
let data: SuperValidated<Infer<FormSchema>> = page.data.datePicker;
export { data as form };
const form = superForm(data, {
validators: zodClient(formSchema),
taintedMessage: null,
onUpdated: ({ form: f }) => {
if (f.valid) {
toast.success(`You submitted ${JSON.stringify(f.data, null, 2)}`);
} else {
toast.error("Please fix the errors in the form.");
}
}
});
const { form: formData, enhance } = form;
const df = new DateFormatter("en-US", {
dateStyle: "long"
});
let value = $state<DateValue | undefined>();
$effect(() => {
value = $formData.dob ? parseDate($formData.dob) : undefined;
});
let placeholder = $state<DateValue>(today(getLocalTimeZone()));
</script>
<form method="POST" action="/?/datePicker" class="space-y-8" use:enhance>
<Form.Field {form} name="dob" class="flex flex-col">
<Form.Control>
{#snippet children({ props })}
<Form.Label>Date of birth</Form.Label>
<Popover.Root>
<Popover.Trigger
{...props}
class={cn(
buttonVariants({ variant: "outline" }),
"w-[280px] justify-start pl-4 text-left font-normal",
!value && "text-muted-foreground"
)}
>
{value
? df.format(value.toDate(getLocalTimeZone()))
: "Pick a date"}
<CalendarIcon class="ml-auto size-4 opacity-50" />
</Popover.Trigger>
<Popover.Content class="w-auto p-0" side="top">
<Calendar
type="single"
value={value as DateValue}
bind:placeholder
minValue={new CalendarDate(1900, 1, 1)}
maxValue={today(getLocalTimeZone())}
calendarLabel="Date of birth"
onValueChange={(v) => {
if (v) {
$formData.dob = v.toString();
} else {
$formData.dob = "";
}
}}
/>
</Popover.Content>
</Popover.Root>
<Form.Description
>Your date of birth is used to calculator your age</Form.Description
>
<Form.FieldErrors />
<input hidden value={$formData.dob} name={props.name} />
{/snippet}
</Form.Control>
</Form.Field>
<Button type="submit">Submit</Button>
{#if browser}
<SuperDebug data={$formData} />
{/if}
</form>
On This Page