A calendar component that allows users to select a date or a range of dates.
1<script module lang="ts">
2 import { Calendar } from "@/components/ui/calendar"
3 import { CalendarDate, type DateValue } from "@internationalized/date"
4 import { type DatePickerValueChangeDetails } from "@sprawlify/svelte/date-picker"
Installation
pnpm dlx sprawlify@latest add calendarnpx sprawlify@latest add calendaryarn sprawlify@latest add calendarbunx --bun sprawlify@latest add calendar
Install the following dependencies:
pnpm add @sprawlify/primitives @sprawlify/sveltenpm install @sprawlify/primitives @sprawlify/svelteyarn add @sprawlify/primitives @sprawlify/sveltebun add @sprawlify/primitives @sprawlify/svelte
Add the following files to your project:
1<script lang="ts">
2 import { DatePicker as DatePickerPrimitive } from "@sprawlify/svelte/date-picker"
3 import { cn } from "@/lib/utils"
4 import type { ComponentProps, Snippet } from "svelte"
5
6 interface CalendarDayButtonProps extends ComponentProps<typeof DatePickerPrimitive.TableCellTrigger> {}
7
8 let {
9 children,
10 class: className,
11 ...props
12 }: CalendarDayButtonProps = $props()
13</script>
14
15<DatePickerPrimitive.TableCellTrigger
16 class={cn(
17 "relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) items-center justify-center gap-1 rounded-(--cell-radius) border-0 text-sm leading-none font-normal",
18 "hover:bg-accent hover:text-accent-foreground",
19 "data-[selected]:bg-primary data-[selected]:text-primary-foreground",
20 "data-[today]:bg-accent data-[today]:text-accent-foreground",
21 "data-[outside-range]:text-muted-foreground/50",
22 "data-[disabled]:text-muted-foreground data-[disabled]:opacity-50",
23 "data-[unavailable]:text-muted-foreground data-[unavailable]:line-through data-[unavailable]:opacity-40",
24 "data-[in-range]:rounded-none data-[in-range]:bg-accent data-[in-range]:text-accent-foreground",
25 "data-[range-start]:rounded-l-(--cell-radius) data-[range-start]:bg-primary data-[range-start]:text-primary-foreground",
26 "data-[range-end]:rounded-r-(--cell-radius) data-[range-end]:bg-primary data-[range-end]:text-primary-foreground",
27 "focus-visible:relative focus-visible:z-10 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1",
28 className
29 )}
30 {...props}
31>
32 {#if children}
33 {@render children()}
34 {/if}
35</DatePickerPrimitive.TableCellTrigger>1<script lang="ts">
2 import { DatePicker as DatePickerPrimitive } from "@sprawlify/svelte/date-picker"
3 import { cn } from "@/lib/utils"
4 import CalendarDayButton from "./calendar-day-button.svelte"
5 import type { DateValue } from "@internationalized/date"
6 import type { Snippet } from "svelte"
7
8 interface CalendarDayTableProps {
9 weeks: DateValue[][]
10 weekDays: { short: string }[]
11 focusedMonth: number
12 showOutsideDays?: boolean
13 visibleRange?: { start: DateValue; end: DateValue }
14 cell?: Snippet<[DateValue]>
15 }
16
17 let {
18 weeks,
19 weekDays,
20 focusedMonth,
21 showOutsideDays = true,
22 visibleRange,
23 cell
24 }: CalendarDayTableProps = $props()
25</script>
26
27<DatePickerPrimitive.Table class="w-full border-collapse">
28 <DatePickerPrimitive.TableHead>
29 <DatePickerPrimitive.TableRow class="flex">
30 {#each weekDays as weekDay}
31 <DatePickerPrimitive.TableHeader
32 class="flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none"
33 >
34 {weekDay.short}
35 </DatePickerPrimitive.TableHeader>
36 {/each}
37 </DatePickerPrimitive.TableRow>
38 </DatePickerPrimitive.TableHead>
39 <DatePickerPrimitive.TableBody>
40 {#each weeks as week}
41 <DatePickerPrimitive.TableRow class="mt-2 flex w-full">
42 {#each week as day, dayIndex (dayIndex)}
43 {@const isOutside = day.month !== focusedMonth}
44 {#if !showOutsideDays && isOutside}
45 <td class="flex-1 p-0" aria-hidden="true"></td>
46 {:else}
47 <DatePickerPrimitive.TableCell
48 value={day}
49 {visibleRange}
50 class={cn(
51 "group/day relative aspect-square h-full w-full flex-1 rounded-(--cell-radius) p-0 text-center select-none",
52 "[&:last-child[data-selected]_div]:rounded-r-(--cell-radius)",
53 "[&:first-child[data-selected]_div]:rounded-l-(--cell-radius)"
54 )}
55 >
56 <CalendarDayButton>
57 {#if cell}
58 {@render cell(day)}
59 {:else}
60 {day.day}
61 {/if}
62 </CalendarDayButton>
63 </DatePickerPrimitive.TableCell>
64 {/if}
65 {/each}
66 </DatePickerPrimitive.TableRow>
67 {/each}
68 </DatePickerPrimitive.TableBody>
69</DatePickerPrimitive.Table>1<script lang="ts">
2 import { DatePicker } from "@sprawlify/svelte/date-picker"
3 import CalendarSelectHeader from "./calendar-select-header.svelte"
4 import CalendarDayTable from "./calendar-day-table.svelte"
5 import type { DateValue } from "@internationalized/date"
6 import type { Snippet } from "svelte"
7
8 interface CalendarDayViewProps {
9 showOutsideDays?: boolean
10 header?: Snippet
11 cell?: Snippet<[DateValue]>
12 }
13
14 let {
15 showOutsideDays = true,
16 header,
17 cell
18 }: CalendarDayViewProps = $props()
19</script>
20
21<DatePicker.View view="day" class="flex flex-col gap-4">
22 <DatePicker.Context>
23 {#snippet render(api)}
24 {#if header}
25 {@render header()}
26 {:else}
27 <CalendarSelectHeader />
28 {/if}
29 <CalendarDayTable
30 weeks={api().weeks}
31 weekDays={api().weekDays}
32 focusedMonth={api().focusedValue.month}
33 {showOutsideDays}
34 {cell}
35 />
36 {/snippet}
37 </DatePicker.Context>
38</DatePicker.View>1<script lang="ts">
2 import { DatePicker } from "@sprawlify/svelte/date-picker"
3 import CalendarDayTable from "./calendar-day-table.svelte"
4 import Button from "../button/button.svelte"
5 import { ChevronLeft, ChevronRight } from "lucide-svelte"
6 import type { DateValue } from "@internationalized/date"
7 import type { Snippet } from "svelte"
8
9 interface CalendarDualMonthDayViewProps {
10 showOutsideDays?: boolean
11 cell?: Snippet<[DateValue]>
12 }
13
14 let {
15 showOutsideDays = true,
16 cell
17 }: CalendarDualMonthDayViewProps = $props()
18</script>
19
20<DatePicker.View view="day" class="flex flex-col gap-4">
21 <DatePicker.Context>
22 {#snippet render(api)}
23 {@const offset = api().getOffset({ months: 1 })}
24 <div class="flex gap-4 flex-row">
25 <!-- First month -->
26 <div class="flex flex-col gap-4">
27 <div class="relative flex w-full items-center justify-between gap-1">
28 <DatePicker.PrevTrigger>
29 {#snippet asChild(props)}
30 <Button
31 variant="ghost"
32 size="icon"
33 class="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"
34 {...props()}
35 >
36 <ChevronLeft class="size-4" />
37 </Button>
38 {/snippet}
39 </DatePicker.PrevTrigger>
40 <span class="text-sm font-medium select-none">
41 {api().visibleRangeText.start}
42 </span>
43 <div class="size-(--cell-size)"></div>
44 </div>
45 <CalendarDayTable
46 weeks={api().weeks}
47 weekDays={api().weekDays}
48 focusedMonth={api().focusedValue.month}
49 {showOutsideDays}
50 {cell}
51 />
52 </div>
53 <!-- Second month -->
54 <div class="flex flex-col gap-4">
55 <div class="relative flex w-full items-center justify-between gap-1">
56 <div class="size-(--cell-size)"></div>
57 <span class="text-sm font-medium select-none">
58 {api().visibleRangeText.end}
59 </span>
60 <DatePicker.NextTrigger>
61 {#snippet asChild(props)}
62 <Button
63 variant="ghost"
64 size="icon"
65 class="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"
66 {...props()}
67 >
68 <ChevronRight class="size-4" />
69 </Button>
70 {/snippet}
71 </DatePicker.NextTrigger>
72 </div>
73 <CalendarDayTable
74 weeks={offset.weeks}
75 weekDays={api().weekDays}
76 focusedMonth={offset.visibleRange.start.month}
77 {showOutsideDays}
78 visibleRange={offset.visibleRange}
79 {cell}
80 />
81 </div>
82 </div>
83 {/snippet}
84 </DatePicker.Context>
85</DatePicker.View>1<script lang="ts">
2 import { DatePicker } from "@sprawlify/svelte/date-picker"
3 import CalendarSelectHeader from "./calendar-select-header.svelte"
4 import Button from "../button/button.svelte"
5 import type { Snippet } from "svelte"
6
7 interface CalendarMonthViewProps {
8 header?: Snippet
9 cell?: Snippet<[{ label: string; value: number }]>
10 }
11
12 let {
13 header,
14 cell
15 }: CalendarMonthViewProps = $props()
16</script>
17
18<DatePicker.View view="month" class="flex flex-col gap-4">
19 <DatePicker.Context>
20 {#snippet render(api)}
21 {#if header}
22 {@render header()}
23 {:else}
24 <CalendarSelectHeader />
25 {/if}
26 <DatePicker.Table class="w-full border-collapse">
27 <DatePicker.TableBody>
28 {#each api().getMonthsGrid({ columns: 4, format: "short" }) as months}
29 <DatePicker.TableRow class="mt-2 flex w-full">
30 {#each months as month}
31 <DatePicker.TableCell
32 value={month.value}
33 class="flex-1 p-0 text-center"
34 >
35 <DatePicker.TableCellTrigger>
36 {#snippet asChild(props)}
37 <Button
38 variant="ghost"
39 class="w-full text-sm font-normal"
40 {...props()}
41 >
42 {#if cell}
43 {@render cell(month)}
44 {:else}
45 {month.label}
46 {/if}
47 </Button>
48 {/snippet}
49 </DatePicker.TableCellTrigger>
50 </DatePicker.TableCell>
51 {/each}
52 </DatePicker.TableRow>
53 {/each}
54 </DatePicker.TableBody>
55 </DatePicker.Table>
56 {/snippet}
57 </DatePicker.Context>
58</DatePicker.View>1<script lang="ts">
2 import { DatePicker } from "@sprawlify/svelte/date-picker";
3 import type { ComponentProps } from "svelte";
4
5 interface CalendarPresetTriggerProps
6 extends ComponentProps<typeof DatePicker.PresetTrigger> {}
7
8 let { children, ...props }: CalendarPresetTriggerProps = $props();
9</script>
10
11<DatePicker.PresetTrigger {...props}>
12 {@render children?.()}
13</DatePicker.PresetTrigger>
141<script lang="ts">
2 import { DatePicker } from "@sprawlify/svelte/date-picker";
3 import { Button } from "@/components/ui/button";
4 import { ChevronLeft, ChevronRight, ChevronDown } from "lucide-svelte";
5 import { cn } from "@/lib/utils";
6 import type { ComponentProps } from "svelte";
7
8 interface CalendarSelectHeaderProps {
9 buttonVariant?: ComponentProps<typeof Button>["variant"];
10 class?: string;
11 }
12
13 let { buttonVariant = "ghost", class: className }: CalendarSelectHeaderProps =
14 $props();
15</script>
16
17<DatePicker.ViewControl
18 class={cn(
19 "relative flex w-full items-center justify-between gap-1",
20 className,
21 )}
22>
23 <Button
24 variant={buttonVariant}
25 size="icon"
26 class="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"
27 >
28 {#snippet asChild(props)}
29 <DatePicker.PrevTrigger {...props()}>
30 <ChevronLeft class="size-4" />
31 </DatePicker.PrevTrigger>
32 {/snippet}
33 </Button>
34
35 <div class="flex items-center gap-1">
36 <span class="relative">
37 <DatePicker.MonthSelect
38 class="appearance-none rounded-md bg-transparent py-1 pr-6 pl-2 text-sm font-medium outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50"
39 />
40 <ChevronDown
41 class="pointer-events-none absolute top-1/2 right-1 size-3.5 -translate-y-1/2 text-muted-foreground"
42 />
43 </span>
44 <span class="relative">
45 <DatePicker.YearSelect
46 class="appearance-none rounded-md bg-transparent py-1 pr-6 pl-2 text-sm font-medium outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50"
47 />
48 <ChevronDown
49 class="pointer-events-none absolute top-1/2 right-1 size-3.5 -translate-y-1/2 text-muted-foreground"
50 />
51 </span>
52 </div>
53
54 <Button
55 variant={buttonVariant}
56 size="icon"
57 class="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"
58 >
59 {#snippet asChild(props)}
60 <DatePicker.NextTrigger {...props()}>
61 <ChevronRight class="size-4" />
62 </DatePicker.NextTrigger>
63 {/snippet}
64 </Button>
65</DatePicker.ViewControl>
661<script lang="ts">
2 import { DatePicker } from "@sprawlify/svelte/date-picker"
3 import { Button } from "@/components/ui/button"
4 import { ChevronLeft, ChevronRight } from "lucide-svelte"
5 import type { ComponentProps } from "svelte";
6
7 interface CalendarViewHeaderProps {
8 buttonVariant?: ComponentProps<typeof Button>["variant"]
9 }
10
11 let { buttonVariant = "ghost" }: CalendarViewHeaderProps = $props()
12</script>
13
14<DatePicker.ViewControl class="relative flex w-full items-center justify-between gap-1">
15 <Button
16 variant={buttonVariant}
17 size="icon"
18 class="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"
19 >
20 {#snippet asChild(props)}
21 <DatePicker.PrevTrigger {...props()}>
22 <ChevronLeft class="size-4" />
23 </DatePicker.PrevTrigger>
24 {/snippet}
25 </Button>
26
27 <DatePicker.ViewTrigger class="flex h-(--cell-size) items-center justify-center text-sm font-medium select-none">
28 <DatePicker.RangeText />
29 </DatePicker.ViewTrigger>
30
31 <Button
32 variant={buttonVariant}
33 size="icon"
34 class="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"
35 >
36 {#snippet asChild(props)}
37 <DatePicker.NextTrigger {...props()}>
38 <ChevronRight class="size-4" />
39 </DatePicker.NextTrigger>
40 {/snippet}
41 </Button>
42</DatePicker.ViewControl>1<script lang="ts">
2 import { DatePicker } from "@sprawlify/svelte/date-picker"
3 import CalendarSelectHeader from "./calendar-select-header.svelte"
4 import Button from "../button/button.svelte"
5 import type { Snippet } from "svelte"
6
7 interface CalendarYearViewProps {
8 header?: Snippet
9 cell?: Snippet<[{ label: string; value: number }]>
10 }
11
12 let {
13 header,
14 cell
15 }: CalendarYearViewProps = $props()
16</script>
17
18<DatePicker.View view="year" class="flex flex-col gap-4">
19 <DatePicker.Context>
20 {#snippet render(api)}
21 {#if header}
22 {@render header()}
23 {:else}
24 <CalendarSelectHeader />
25 {/if}
26 <DatePicker.Table class="w-full border-collapse">
27 <DatePicker.TableBody>
28 {#each api().getYearsGrid({ columns: 4 }) as years}
29 <DatePicker.TableRow class="mt-2 flex w-full">
30 {#each years as year}
31 <DatePicker.TableCell
32 value={year.value}
33 class="flex-1 p-0 text-center"
34 >
35 <DatePicker.TableCellTrigger>
36 {#snippet asChild(props)}
37 <Button
38 variant="ghost"
39 class="w-full text-sm font-normal"
40 {...props()}
41 >
42 {#if cell}
43 {@render cell(year)}
44 {:else}
45 {year.label}
46 {/if}
47 </Button>
48 {/snippet}
49 </DatePicker.TableCellTrigger>
50 </DatePicker.TableCell>
51 {/each}
52 </DatePicker.TableRow>
53 {/each}
54 </DatePicker.TableBody>
55 </DatePicker.Table>
56 {/snippet}
57 </DatePicker.Context>
58</DatePicker.View>1<script lang="ts">
2 import { cn } from "@/lib/utils"
3 import { DatePicker as DatePickerPrimitive } from "@sprawlify/svelte/date-picker"
4 import type { ComponentProps, Snippet } from "svelte"
5 import CalendarDayView from "./calendar-day-view.svelte"
6 import CalendarDualMonthDayView from "./calendar-dual-month-day-view.svelte"
7 import CalendarMonthView from "./calendar-month-view.svelte"
8 import CalendarYearView from "./calendar-year-view.svelte"
9
10 interface CalendarProps extends Omit<ComponentProps<typeof DatePickerPrimitive.Root>, "inline"> {
11 showOutsideDays?: boolean
12 children?: Snippet
13 }
14
15 let {
16 class: className,
17 showOutsideDays = true,
18 children,
19 numOfMonths,
20 ...props
21 }: CalendarProps = $props()
22
23 const isDualMonth = () => numOfMonths && numOfMonths >= 2
24</script>
25
26<DatePickerPrimitive.Root inline {numOfMonths} {...props}>
27 <div
28 data-slot="calendar"
29 class={cn(
30 "group/calendar w-fit bg-background p-2 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
31 className
32 )}
33 >
34 {#if children}
35 {@render children()}
36 {:else}
37 {#if isDualMonth()}
38 <CalendarDualMonthDayView {showOutsideDays} />
39 {:else}
40 <CalendarDayView {showOutsideDays} />
41 {/if}
42 <CalendarMonthView />
43 <CalendarYearView />
44 {/if}
45 </div>
46</DatePickerPrimitive.Root>1export { default as Calendar } from "./calendar.svelte";2export { default as CalendarDayView } from "./calendar-day-view.svelte";3export { default as CalendarDualMonthDayView } from "./calendar-dual-month-day-view.svelte";4export { default as CalendarDayTable } from "./calendar-day-table.svelte";5export { default as CalendarMonthView } from "./calendar-month-view.svelte";6export { default as CalendarYearView } from "./calendar-year-view.svelte";7export { default as CalendarViewHeader } from "./calendar-view-header.svelte";8export { default as CalendarSelectHeader } from "./calendar-select-header.svelte";9export { default as CalendarDayButton } from "./calendar-day-button.svelte";10export { default as CalendarPresetTrigger } from "./calendar-preset-trigger.svelte";11Update the import paths to match your project setup.
Usage
1import { Calendar } from "@/components/ui/calendar"1<script module lang="ts">
2 let date = $state([new CalendarDate(2026, 3, 21)])
3
4 function handleValueChange(details) {
5 date = details.value
6 }
7</script>
8
9<Calendar
10 selectionMode="single"
11 value={date}
12 onValueChange={handleValueChange}
13 class="rounded-lg border"
14/>Examples
Basic
1<script module lang="ts">
2 import { Calendar } from "@/components/ui/calendar"
3</script>
4
Calendar Booked Dates
1<script module lang="ts">
2 import { Calendar } from "@/components/ui/calendar"
3 import { Card, CardContent } from "@/components/ui/card"
4 import { CalendarDate, type DateValue } from "@internationalized/date"
Date And Time Picker
1<script module lang="ts">
2 import { Calendar } from "@/components/ui/calendar"
3 import { Card, CardContent, CardFooter } from "@/components/ui/card"
4 import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
Presets
1<script module lang="ts">
2 import { Button } from "@/components/ui/button"
3 import {
4 Calendar,
Range Calendar
1<script module lang="ts">
2 import { Calendar } from "@/components/ui/calendar"
3 import { Card, CardContent } from "@/components/ui/card"
4 import { CalendarDate, type DateValue } from "@internationalized/date"
Views
1<script>
2 import {
3 Calendar,
4 CalendarDayView,
On This Page
InstallationUsageExamplesBasicCalendar Booked DatesDate And Time PickerPresetsRange CalendarViewsGet PRO
Need premium blocks and templates? Upgrade to PRO and get access.