Docs
Calendar
Calendar
A calendar component that allows users to select dates.
Loading...
<script lang="ts">
import { Calendar } from "$lib/components/ui/calendar";
import { today, getLocalTimeZone } from "@internationalized/date";
let value = today(getLocalTimeZone());
</script>
<Calendar bind:value class="border rounded-md shadow" />
<script lang="ts">
import { Calendar } from "$lib/components/ui/calendar";
import { today, getLocalTimeZone } from "@internationalized/date";
let value = today(getLocalTimeZone());
</script>
<Calendar bind:value class="border rounded-md" />
About
The <Calendar />
component is built on top of the Bits Calendar component, which uses the @internationalized/date package to handle dates.
If you're looking for a range calendar, check out the Range Calendar component.
Installation
npx shadcn-svelte@latest add calendar
Date Picker
You can use the <Calendar />
component to build a date picker. See the Date Picker page for more information.
Examples
Form
Loading...
<script lang="ts">
import { Calendar as CalendarIcon } from "radix-icons-svelte";
import {
type DateValue,
DateFormatter,
getLocalTimeZone
} from "@internationalized/date";
import { cn } from "$lib/utils";
import { Button } from "$lib/components/ui/button";
import { Calendar } from "$lib/components/ui/calendar";
import * as Popover from "$lib/components/ui/popover";
const df = new DateFormatter("en-US", {
dateStyle: "long"
});
let value: DateValue | undefined = undefined;
</script>
<Popover.Root>
<Popover.Trigger asChild let:builder>
<Button
variant="outline"
class={cn(
"w-[240px] justify-start text-left font-normal",
!value && "text-muted-foreground"
)}
builders={[builder]}
>
<CalendarIcon class="mr-2 h-4 w-4" />
{value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date"}
</Button>
</Popover.Trigger>
<Popover.Content class="w-auto p-0" align="start">
<Calendar bind:value />
</Popover.Content>
</Popover.Root>
<script lang="ts">
import { Calendar as CalendarIcon } from "lucide-svelte";
import {
type DateValue,
DateFormatter,
getLocalTimeZone
} from "@internationalized/date";
import { cn } from "$lib/utils";
import { Button } from "$lib/components/ui/button";
import { Calendar } from "$lib/components/ui/calendar";
import * as Popover from "$lib/components/ui/popover";
const df = new DateFormatter("en-US", {
dateStyle: "long"
});
let value: DateValue | undefined = undefined;
</script>
<Popover.Root>
<Popover.Trigger asChild let:builder>
<Button
variant="outline"
class={cn(
"w-[280px] justify-start text-left font-normal",
!value && "text-muted-foreground"
)}
builders={[builder]}
>
<CalendarIcon class="mr-2 h-4 w-4" />
{value ? df.format(value.toDate(getLocalTimeZone())) : "Pick a date"}
</Button>
</Popover.Trigger>
<Popover.Content class="w-auto p-0">
<Calendar bind:value initialFocus />
</Popover.Content>
</Popover.Root>
Advanced Customization
The <Calendar />
component can be combined with other components to create a more complex calendar.
By default, we export the combined Calendar component as
Calendar
as there are quite a few pieces that need to be combined to create it. We're modifying that component in the examples below.Month & Year Selects
Here's an example of how you could create a calendar with month and year select dropdowns instead of the previous and next buttons.
Loading...
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import * as Calendar from "$lib/components/ui/calendar";
import * as Select from "$lib/components/ui/select";
import { cn } from "$lib/utils";
import {
DateFormatter,
getLocalTimeZone,
today
} from "@internationalized/date";
type $$Props = CalendarPrimitive.Props;
type $$Events = CalendarPrimitive.Events;
export let value: $$Props["value"] = undefined;
export let placeholder: $$Props["placeholder"] = today(getLocalTimeZone());
export let weekdayFormat: $$Props["weekdayFormat"] = "short";
const monthOptions = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
].map((month, i) => ({ value: i + 1, label: month }));
const monthFmt = new DateFormatter("en-US", {
month: "long"
});
const yearOptions = Array.from({ length: 100 }, (_, i) => ({
label: String(new Date().getFullYear() - i),
value: new Date().getFullYear() - i
}));
$: defaultYear = placeholder
? {
value: placeholder.year,
label: String(placeholder.year)
}
: undefined;
$: defaultMonth = placeholder
? {
value: placeholder.month,
label: monthFmt.format(placeholder.toDate(getLocalTimeZone()))
}
: undefined;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.Root
bind:value
bind:placeholder
{weekdayFormat}
class={cn("p-3 rounded-md border", className)}
{...$$restProps}
on:keydown
let:months
let:weekdays
>
<Calendar.Header>
<Calendar.Heading class="flex items-center justify-between w-full gap-2">
<Select.Root
selected={defaultMonth}
items={monthOptions}
onSelectedChange={(v) => {
if (!v || !placeholder) return;
if (v.value === placeholder?.month) return;
placeholder = placeholder.set({ month: v.value });
}}
>
<Select.Trigger aria-label="Select month" class="w-[60%]">
<Select.Value placeholder="Select month" />
</Select.Trigger>
<Select.Content class="max-h-[200px] overflow-y-auto">
{#each monthOptions as { value, label }}
<Select.Item {value} {label}>
{label}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
<Select.Root
selected={defaultYear}
items={yearOptions}
onSelectedChange={(v) => {
if (!v || !placeholder) return;
if (v.value === placeholder?.year) return;
placeholder = placeholder.set({ year: v.value });
}}
>
<Select.Trigger aria-label="Select year" class="w-[40%]">
<Select.Value placeholder="Select year" />
</Select.Trigger>
<Select.Content class="max-h-[200px] overflow-y-auto">
{#each yearOptions as { value, label }}
<Select.Item {value} {label}>
{label}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</Calendar.Heading>
</Calendar.Header>
<Calendar.Months>
{#each months as month}
<Calendar.Grid>
<Calendar.GridHead>
<Calendar.GridRow class="flex">
{#each weekdays as weekday}
<Calendar.HeadCell>
{weekday.slice(0, 2)}
</Calendar.HeadCell>
{/each}
</Calendar.GridRow>
</Calendar.GridHead>
<Calendar.GridBody>
{#each month.weeks as weekDates}
<Calendar.GridRow class="w-full mt-2">
{#each weekDates as date}
<Calendar.Cell {date}>
<Calendar.Day {date} month={month.value} />
</Calendar.Cell>
{/each}
</Calendar.GridRow>
{/each}
</Calendar.GridBody>
</Calendar.Grid>
{/each}
</Calendar.Months>
</CalendarPrimitive.Root>
<script lang="ts">
import { Calendar as CalendarPrimitive } from "bits-ui";
import * as Calendar from "$lib/components/ui/calendar";
import * as Select from "$lib/components/ui/select";
import { cn } from "$lib/utils";
import {
DateFormatter,
getLocalTimeZone,
today
} from "@internationalized/date";
type $$Props = CalendarPrimitive.Props;
type $$Events = CalendarPrimitive.Events;
export let value: $$Props["value"] = undefined;
export let placeholder: $$Props["placeholder"] = today(getLocalTimeZone());
export let weekdayFormat: $$Props["weekdayFormat"] = "short";
const monthOptions = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
].map((month, i) => ({ value: i + 1, label: month }));
const monthFmt = new DateFormatter("en-US", {
month: "long"
});
const yearOptions = Array.from({ length: 100 }, (_, i) => ({
label: String(new Date().getFullYear() - i),
value: new Date().getFullYear() - i
}));
$: defaultYear = placeholder
? {
value: placeholder.year,
label: String(placeholder.year)
}
: undefined;
$: defaultMonth = placeholder
? {
value: placeholder.month,
label: monthFmt.format(placeholder.toDate(getLocalTimeZone()))
}
: undefined;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<CalendarPrimitive.Root
{weekdayFormat}
class={cn("p-3 rounded-md border", className)}
{...$$restProps}
on:keydown
let:months
let:weekdays
bind:value
bind:placeholder
>
<Calendar.Header>
<Calendar.Heading class="flex items-center justify-between w-full gap-2">
<Select.Root
selected={defaultMonth}
items={monthOptions}
onSelectedChange={(v) => {
if (!v || !placeholder) return;
if (v.value === placeholder?.month) return;
placeholder = placeholder.set({ month: v.value });
}}
>
<Select.Trigger aria-label="Select month" class="w-[60%]">
<Select.Value placeholder="Select month" />
</Select.Trigger>
<Select.Content class="max-h-[200px] overflow-y-auto">
{#each monthOptions as { value, label }}
<Select.Item {value} {label}>
{label}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
<Select.Root
selected={defaultYear}
items={yearOptions}
onSelectedChange={(v) => {
if (!v || !placeholder) return;
if (v.value === placeholder?.year) return;
placeholder = placeholder.set({ year: v.value });
}}
>
<Select.Trigger aria-label="Select year" class="w-[40%]">
<Select.Value placeholder="Select year" />
</Select.Trigger>
<Select.Content class="max-h-[200px] overflow-y-auto">
{#each yearOptions as { value, label }}
<Select.Item {value} {label}>
{label}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</Calendar.Heading>
</Calendar.Header>
<Calendar.Months>
{#each months as month}
<Calendar.Grid>
<Calendar.GridHead>
<Calendar.GridRow class="flex">
{#each weekdays as weekday}
<Calendar.HeadCell>
{weekday.slice(0, 2)}
</Calendar.HeadCell>
{/each}
</Calendar.GridRow>
</Calendar.GridHead>
<Calendar.GridBody>
{#each month.weeks as weekDates}
<Calendar.GridRow class="w-full mt-2">
{#each weekDates as date}
<Calendar.Cell {date}>
<Calendar.Day {date} month={month.value} />
</Calendar.Cell>
{/each}
</Calendar.GridRow>
{/each}
</Calendar.GridBody>
</Calendar.Grid>
{/each}
</Calendar.Months>
</CalendarPrimitive.Root>
On This Page