8.0k New

Calendar

A calendar component that allows users to select a date or a range of dates.

Docs API Reference

Epicenter

Local-first, open source apps

Special Sponsor
SuMoTuWeThFrSa
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script lang="ts">
	import { getLocalTimeZone, today, type CalendarDate } from "@internationalized/date";
	import { Calendar } from "$lib/components/ui/calendar/index.js";
 
	let value = $state<CalendarDate | undefined>(today(getLocalTimeZone()));
</script>
 
<Calendar type="single" bind:value class="rounded-lg border" captionLayout="dropdown" />

Installation

pnpm dlx shadcn-svelte@latest add calendar

Usage

<script lang="ts">
  import { getLocalTimeZone, today } from "@internationalized/date";
  import { Calendar } from "$lib/components/ui/calendar/index.js";
 
  let value = $state(today(getLocalTimeZone()));
</script>
<Calendar type="single" bind:value class="rounded-lg border" />

See the Bits UI Calendar documentation for more information.

About

The <Calendar /> component is built on top of the Bits UI Calendar component, which uses the @internationalized/date package to handle dates.

Date Picker

You can use the <Calendar /> component to build a date picker. See the Date Picker page for more information.

Basic

A basic calendar component. We used class="rounded-lg border" to style the calendar.

October 2026
SuMoTuWeThFrSa
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script lang="ts">
	import { Calendar } from "$lib/components/ui/calendar/index.js";
</script>
 
<Calendar type="single" class="rounded-lg border" />

Range Calendar

Use the RangeCalendar component to enable range selection.

January 2026
SuMoTuWeThFrSa
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
February 2026
SuMoTuWeThFrSa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<script lang="ts">
	import { CalendarDate, getLocalTimeZone, today } from "@internationalized/date";
	import * as Card from "$lib/components/ui/card/index.js";
	import { RangeCalendar } from "$lib/components/ui/range-calendar/index.js";
	import type { DateRange } from "bits-ui";
 
	const start = new CalendarDate(today(getLocalTimeZone()).year, 1, 12);
 
	let value = $state<DateRange | undefined>({
		start,
		end: start.add({ days: 30 }),
	});
</script>
 
<Card.Root class="mx-auto w-fit p-0">
	<Card.Content class="p-0">
		<RangeCalendar
			bind:value
			numberOfMonths={2}
			minValue={new CalendarDate(1900, 1, 1)}
			maxValue={today(getLocalTimeZone())}
		/>
	</Card.Content>
</Card.Root>

Month and Year Selector

Use captionLayout="dropdown" to show month and year dropdowns.

SuMoTuWeThFrSa
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script lang="ts">
	import { Calendar } from "$lib/components/ui/calendar/index.js";
</script>
 
<Calendar type="single" captionLayout="dropdown" class="rounded-lg border" />

Presets

October 2026
SuMoTuWeThFrSa
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
<script lang="ts">
	import { CalendarDate, getLocalTimeZone, today } from "@internationalized/date";
	import * as Card from "$lib/components/ui/card/index.js";
	import { Button } from "$lib/components/ui/button/index.js";
	import { Calendar } from "$lib/components/ui/calendar/index.js";
 
	const presets = [
		{ label: "Today", value: 0 },
		{ label: "Tomorrow", value: 1 },
		{ label: "In 3 days", value: 3 },
		{ label: "In a week", value: 7 },
		{ label: "In 2 weeks", value: 14 },
	];
 
	let value = $state<CalendarDate | undefined>(
		new CalendarDate(today(getLocalTimeZone()).year, 2, 12)
	);
	let placeholder = $state<CalendarDate>(today(getLocalTimeZone()));
</script>
 
<Card.Root class="mx-auto w-fit max-w-[300px]" size="sm">
	<Card.Content>
		<Calendar
			type="single"
			bind:value
			bind:placeholder
			fixedWeeks
			class="p-0 [--cell-size:--spacing(9.5)]"
		/>
	</Card.Content>
	<Card.Footer class="flex flex-wrap gap-2 border-t">
		{#each presets as preset (preset.value)}
			<Button
				variant="outline"
				size="sm"
				class="flex-1"
				onclick={() => {
					const newDate = today(getLocalTimeZone()).add({ days: preset.value });
					value = newDate;
					placeholder = newDate;
				}}
			>
				{preset.label}
			</Button>
		{/each}
	</Card.Footer>
</Card.Root>

Date and Time Picker

October 2026
SuMoTuWeThFrSa
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script lang="ts">
	import Clock2Icon from "@lucide/svelte/icons/clock-2";
	import { CalendarDate, getLocalTimeZone, today } from "@internationalized/date";
	import * as Card from "$lib/components/ui/card/index.js";
	import * as Field from "$lib/components/ui/field/index.js";
	import * as InputGroup from "$lib/components/ui/input-group/index.js";
	import { Calendar } from "$lib/components/ui/calendar/index.js";
 
	const now = today(getLocalTimeZone());
 
	let value = $state<CalendarDate | undefined>(new CalendarDate(now.year, now.month, 12));
</script>
 
<Card.Root size="sm" class="mx-auto w-fit">
	<Card.Content>
		<Calendar type="single" bind:value class="p-0" />
	</Card.Content>
	<Card.Footer class="border-t bg-card">
		<Field.Group>
			<Field.Field>
				<Field.Label for="time-from">Start Time</Field.Label>
				<InputGroup.Root>
					<InputGroup.Input
						id="time-from"
						type="time"
						step="1"
						value="10:30:00"
						class="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
					/>
					<InputGroup.Addon>
						<Clock2Icon class="text-muted-foreground" />
					</InputGroup.Addon>
				</InputGroup.Root>
			</Field.Field>
			<Field.Field>
				<Field.Label for="time-to">End Time</Field.Label>
				<InputGroup.Root>
					<InputGroup.Input
						id="time-to"
						type="time"
						step="1"
						value="12:30:00"
						class="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
					/>
					<InputGroup.Addon>
						<Clock2Icon class="text-muted-foreground" />
					</InputGroup.Addon>
				</InputGroup.Root>
			</Field.Field>
		</Field.Group>
	</Card.Footer>
</Card.Root>

Booked dates

February 2026
SuMoTuWeThFrSa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<script lang="ts">
	import { CalendarDate, getLocalTimeZone, today, type DateValue } from "@internationalized/date";
	import * as Card from "$lib/components/ui/card/index.js";
	import { Calendar } from "$lib/components/ui/calendar/index.js";
 
	const year = today(getLocalTimeZone()).year;
 
	let value = $state<CalendarDate | undefined>(new CalendarDate(year, 2, 3));
 
	const bookedDates = Array.from({ length: 15 }, (_, i) => new CalendarDate(year, 2, 12 + i));
 
	function isBooked(date: DateValue) {
		return bookedDates.some((d) => d.compare(date) === 0);
	}
</script>
 
<Card.Root class="mx-auto w-fit p-0">
	<Card.Content class="p-0">
		<Calendar type="single" bind:value isDateUnavailable={isBooked} />
	</Card.Content>
</Card.Root>

Custom Cell Size

SuMoTuWeThFrSa
29
30
1 $100
2 $100
3 $100
4 $100
5 $120
6 $120
7 $100
8 $100
9 $100
10 $100
11 $100
12 $120
13 $120
14 $100
15 $100
16 $100
17 $100
18 $100
19 $120
20 $120
21 $100
22 $100
23 $100
24 $100
25 $100
26 $120
27 $120
28 $100
29 $100
30 $100
31 $100
1
2
<script lang="ts">
	import { CalendarDate, getLocalTimeZone, isWeekend, today } from "@internationalized/date";
	import * as Card from "$lib/components/ui/card/index.js";
	import RangeCalendarDay from "$lib/components/ui/range-calendar/range-calendar-day.svelte";
	import { RangeCalendar } from "$lib/components/ui/range-calendar/index.js";
	import type { DateRange } from "bits-ui";
 
	const start = new CalendarDate(today(getLocalTimeZone()).year, 12, 8);
 
	let value = $state<DateRange | undefined>({
		start,
		end: start.add({ days: 10 }),
	});
</script>
 
<Card.Root class="mx-auto w-fit p-0">
	<Card.Content class="p-0">
		<RangeCalendar
			bind:value
			captionLayout="dropdown"
			monthFormat="long"
			class="[--cell-size:--spacing(10)] md:[--cell-size:--spacing(12)]"
		>
			{#snippet day({ day, outsideMonth })}
				<RangeCalendarDay>
					{day.day}
					{#if !outsideMonth}
						<span>{isWeekend(day, "en-US") ? "$120" : "$100"}</span>
					{/if}
				</RangeCalendarDay>
			{/snippet}
		</RangeCalendar>
	</Card.Content>
</Card.Root>

You can customize the size of calendar cells using the --cell-size CSS variable. You can also make it responsive by using breakpoint-specific values:

<Calendar
  type="single"
  bind:value
  class="rounded-lg border [--cell-size:--spacing(11)] md:[--cell-size:--spacing(12)]"
/>

Or use fixed values:

<Calendar
  type="single"
  bind:value
  class="rounded-lg border [--cell-size:2.75rem] md:[--cell-size:3rem]"
/>