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>
The Date Picker is built using a composition of the <Popover /> and either the <Calendar /> or <RangeCalendar /> components.
<Popover />
<Calendar />
<RangeCalendar />
See installations instructions for the Popover, Calendar, and Range Calendar components.
<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>
<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>
<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" value={valueString} controlledValue onValueChange={(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" value={valueString} controlledValue onValueChange={(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" 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/stores"; 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/stores"; 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