ZaidanHomeDocsComponentsBlocksChartsTypesetCreate

Command Palette

Search for a command to run...

GitHub
New
Sections
  • Introduction
  • Components
  • Blocks
  • Installation
  • Customization
  • Dark Mode
  • Typeset
  • Zaidan Skills
  • FAQ
  • Roadmap
  • Changelog
Installation
  • Astro
  • Manual
  • Solid Start
  • TanStack Router
  • TanStack Start
  • Vite
Blocks
  • Data Grid
  • Event Calendar
  • Filters
  • Gantt
  • Image Crop
  • Kanban
  • Message Scroller
  • Questionnaire
  • Sortable
Components
  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Drawer
  • Dropdown Menu
  • Empty
  • Field
  • Hover Card
  • Input
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Label
  • Marker
  • Menubar
  • Message
  • Native Select
  • Navigation Menu
  • Pagination
  • Popover
  • Progress
  • Radio Group
  • Resizable
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Table
  • Tabs
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip

Calendar

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

Corvu
Corvu

The Calendar component is built on top of Corvu Calendar, which provides the accessible calendar primitive and keyboard navigation.

Aug2026August 2026
SuMoTuWeThFrSa
1
import { createSignal } from "solid-js";
2
import { Calendar } from "~/components/ui/calendar";
3
4
export default function CalendarDemo() {
5
const [date, setDate] = createSignal(new Date());
6
7
return (
8
<Calendar
9
mode="single"
10
selected={date()}
11
onSelect={setDate}
12
class="rounded-lg border"
13
captionLayout="dropdown"
14
/>
15
);
16
}

Installation

pnpm dlx shadcn@latest add @zaidan/calendar
npx shadcn@latest add @zaidan/calendar
yarn dlx shadcn@latest add @zaidan/calendar
bunx --bun shadcn@latest add @zaidan/calendar

Usage

1
import { createSignal } from "solid-js";
2
import { Calendar } from "~/components/ui/calendar";
3
4
const [date, setDate] = createSignal<Date>();
1
<Calendar
2
mode="single"
3
selected={date()}
4
onSelect={setDate}
5
class="rounded-lg border"
6
/>

See the Corvu Calendar documentation for primitive-level options and behavior.

Date Picker

You can use the Calendar component to build a date picker. Zaidan does not yet ship a dedicated Date Picker page or registry item — Date Picker is excluded from the shadcn sync and tracked separately.

Persian Locale

This focused demo renders and selects Persian calendar dates.

۱۴۰۴ خرداد

تقویم فارسی
شنبهیکشنبهدوشنبهسه‌شنبهچهارشنبهپنجشنبهجمعه
Persian\
1
import { CalendarDate, PersianCalendar, startOfWeek } from "@internationalized/date";
2
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-solid";
3
import { createMemo, createSignal, For } from "solid-js";
4
import { Button } from "~/components/ui/button";
5
6
const persianCalendar = new PersianCalendar();
7
const numberFormatter = new Intl.NumberFormat("fa-IR-u-nu-arabext", { useGrouping: false });
8
const monthFormatter = new Intl.DateTimeFormat("fa-IR-u-ca-persian", {
9
month: "long",
10
year: "numeric",
11
timeZone: "UTC",
12
});
13
const dayFormatter = new Intl.DateTimeFormat("fa-IR-u-ca-persian", {
14
dateStyle: "full",
15
timeZone: "UTC",
16
});
17
const weekdayFormatter = new Intl.DateTimeFormat("fa-IR-u-ca-persian", {
18
weekday: "short",
19
timeZone: "UTC",
20
});
21
22
const sameDay = (left: CalendarDate, right: CalendarDate) => left.compare(right) === 0;
23
24
export default function CalendarHijri() {
25
const [month, setMonth] = createSignal(new CalendarDate(persianCalendar, 1404, 3, 1));
26
const [selected, setSelected] = createSignal(new CalendarDate(persianCalendar, 1404, 3, 22));
27
const focusDay = (day: CalendarDate) => {
28
const target = document.querySelector<HTMLButtonElement>(
29
`[data-persian-day="${day.toString()}"]`,
30
);
31
target?.focus();
32
};
33
const onDayKeyDown = (day: CalendarDate, event: KeyboardEvent) => {
34
const offsets: Record<string, number> = {
35
ArrowDown: 7,
36
ArrowLeft: -1,
37
ArrowRight: 1,
38
ArrowUp: -7,
39
};
40
const offset = offsets[event.key];
41
42
if (offset === undefined) return;
43
event.preventDefault();
44
focusDay(day.add({ days: offset }));
45
};
46
const weekdays = createMemo(() => {
47
const firstDay = startOfWeek(month(), "fa-IR");
48
49
return Array.from({ length: 7 }, (_, index) =>
50
weekdayFormatter.format(firstDay.add({ days: index }).toDate("UTC")),
51
);
52
});
53
const days = createMemo(() => {
54
const start = startOfWeek(month(), "fa-IR");
55
return Array.from({ length: 42 }, (_, index) => start.add({ days: index }));
56
});
57
58
return (
59
<div lang="fa" class="w-fit rounded-lg border bg-background p-3 [--cell-size:--spacing(8)]">
60
<div class="mb-4 flex h-(--cell-size) items-center justify-between gap-1">
61
<Button
62
type="button"
63
variant="ghost"
64
size="icon"
65
aria-label="ماه قبل"
66
onClick={() => setMonth((value) => value.subtract({ months: 1 }))}
67
>
68
<ChevronRightIcon />
69
</Button>
70
<p class="text-sm font-medium">{monthFormatter.format(month().toDate("UTC"))}</p>
71
<Button
72
type="button"
73
variant="ghost"
74
size="icon"
75
aria-label="ماه بعد"
76
onClick={() => setMonth((value) => value.add({ months: 1 }))}
77
>
78
<ChevronLeftIcon />
79
</Button>
80
</div>
81
<table class="w-full border-collapse">
82
<caption class="sr-only">تقویم فارسی</caption>
83
<thead>
84
<tr class="flex">
85
<For each={weekdays()}>
86
{(weekday) => (
87
<th
88
scope="col"
89
class="flex-1 text-center text-[0.8rem] font-normal text-muted-foreground"
90
>
91
{weekday}
92
</th>
93
)}
94
</For>
95
</tr>
96
</thead>
97
<tbody>
98
<For each={Array.from({ length: 6 })}>
99
{(_, weekIndex) => (
100
<tr class="mt-2 flex w-full">
101
<For each={days().slice(weekIndex() * 7, weekIndex() * 7 + 7)}>
102
{(day) => {
103
const outside = () => day.month !== month().month;
104
const isSelected = () => sameDay(day, selected());
105
106
return (
107
<td class="relative flex-1 p-0 text-center">
108
<Button
109
type="button"
110
variant="ghost"
111
size="icon"
112
aria-label={dayFormatter.format(day.toDate("UTC"))}
113
aria-selected={isSelected()}
114
data-selected={isSelected() || undefined}
115
data-persian-day={day.toString()}
116
class="size-(--cell-size) font-normal data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground"
117
classList={{ "text-muted-foreground": outside() }}
118
onClick={() => setSelected(day)}
119
onKeyDown={(event) => onDayKeyDown(day, event)}
120
>
121
{numberFormatter.format(day.day)}
122
</Button>
123
</td>
124
);
125
}}
126
</For>
127
</tr>
128
)}
129
</For>
130
</tbody>
131
</table>
132
</div>
133
);
134
}

The primary Calendar wrapper accepts JavaScript Date values. This focused demo uses Solid's @internationalized/date Persian calendar for the alternate calendar arithmetic.

Selected Date (With Time Zone)

Pass timeZone to ensure the selected date uses the user's local time zone. Resolve it on mount when your page is server-rendered.

1
import { createSignal, onMount } from "solid-js";
2
3
const [date, setDate] = createSignal<Date>();
4
const [timeZone, setTimeZone] = createSignal<string>();
5
6
onMount(() => {
7
setTimeZone(Intl.DateTimeFormat().resolvedOptions().timeZone);
8
});
9
10
<Calendar
11
mode="single"
12
selected={date()}
13
onSelect={setDate}
14
timeZone={timeZone()}
15
/>

Basic

A basic calendar component styled with class="rounded-lg border".

August 2026
SuMoTuWeThFrSa
1
import { Calendar } from "~/components/ui/calendar";
2
3
export default function CalendarBasic() {
4
return <Calendar mode="single" class="rounded-lg border" />;
5
}

Range Calendar

Use mode="range" to enable range selection.

January 2026
SuMoTuWeThFrSa
February 2026
SuMoTuWeThFrSa
1
import { addDays } from "date-fns";
2
import { createSignal } from "solid-js";
3
import { Calendar, type CalendarRangeValue } from "~/components/ui/calendar";
4
5
export default function CalendarRange() {
6
const start = new Date(new Date().getFullYear(), 0, 12);
7
const [dateRange, setDateRange] = createSignal<CalendarRangeValue>({
8
from: start,
9
to: addDays(start, 30),
10
});
11
12
return (
13
<Calendar
14
mode="range"
15
defaultMonth={dateRange().from}
16
selected={dateRange()}
17
onSelect={setDateRange}
18
numberOfMonths={2}
19
class="rounded-lg border"
20
/>
21
);
22
}

Month and Year Selector

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

Aug2026August 2026
SuMoTuWeThFrSa
1
import { Calendar } from "~/components/ui/calendar";
2
3
export default function CalendarCaption() {
4
return <Calendar mode="single" captionLayout="dropdown" class="rounded-lg border" />;
5
}

Presets

August 2026
SuMoTuWeThFrSa
1
import { addDays } from "date-fns";
2
import { createSignal, For } from "solid-js";
3
import { Button } from "~/components/ui/button";
4
import { Calendar } from "~/components/ui/calendar";
5
import { Card, CardContent, CardFooter } from "~/components/ui/card";
6
7
const presets = [
8
{ label: "Today", value: 0 },
9
{ label: "Tomorrow", value: 1 },
10
{ label: "In 3 days", value: 3 },
11
{ label: "In a week", value: 7 },
12
{ label: "In 2 weeks", value: 14 },
13
];
14
15
export default function CalendarPresets() {
16
const [date, setDate] = createSignal(new Date(new Date().getFullYear(), 1, 12));
17
const [currentMonth, setCurrentMonth] = createSignal(
18
new Date(new Date().getFullYear(), new Date().getMonth(), 1),
19
);
20
21
const selectPreset = (offset: number) => {
22
const nextDate = addDays(new Date(), offset);
23
setDate(nextDate);
24
setCurrentMonth(new Date(nextDate.getFullYear(), nextDate.getMonth(), 1));
25
};
26
27
return (
28
<Card class="mx-auto w-fit max-w-[300px]" size="sm">
29
<CardContent>
30
<Calendar
31
mode="single"
32
selected={date()}
33
onSelect={setDate}
34
month={currentMonth()}
35
onMonthChange={setCurrentMonth}
36
fixedWeeks
37
class="p-0 [--cell-size:--spacing(9.5)]"
38
/>
39
</CardContent>
40
<CardFooter class="flex flex-wrap gap-2 border-t">
41
<For each={presets}>
42
{(preset) => (
43
<Button
44
variant="outline"
45
size="sm"
46
class="flex-1"
47
onClick={() => selectPreset(preset.value)}
48
>
49
{preset.label}
50
</Button>
51
)}
52
</For>
53
</CardFooter>
54
</Card>
55
);
56
}

Date and Time Picker

August 2026
SuMoTuWeThFrSa
1
import { Clock2Icon } from "lucide-solid";
2
import { createSignal } from "solid-js";
3
import { Calendar } from "~/components/ui/calendar";
4
import { Card, CardContent, CardFooter } from "~/components/ui/card";
5
import { Field, FieldGroup, FieldLabel } from "~/components/ui/field";
6
import { InputGroup, InputGroupAddon, InputGroupInput } from "~/components/ui/input-group";
7
8
export default function CalendarTime() {
9
const [date, setDate] = createSignal(
10
new Date(new Date().getFullYear(), new Date().getMonth(), 12),
11
);
12
13
return (
14
<Card size="sm" class="mx-auto w-fit">
15
<CardContent>
16
<Calendar mode="single" selected={date()} onSelect={setDate} class="p-0" />
17
</CardContent>
18
<CardFooter class="border-t bg-card">
19
<FieldGroup>
20
<Field>
21
<FieldLabel for="time-from">Start Time</FieldLabel>
22
<InputGroup>
23
<InputGroupInput
24
id="time-from"
25
type="time"
26
step="1"
27
value="10:30:00"
28
class="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
29
/>
30
<InputGroupAddon>
31
<Clock2Icon class="text-muted-foreground" />
32
</InputGroupAddon>
33
</InputGroup>
34
</Field>
35
<Field>
36
<FieldLabel for="time-to">End Time</FieldLabel>
37
<InputGroup>
38
<InputGroupInput
39
id="time-to"
40
type="time"
41
step="1"
42
value="12:30:00"
43
class="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
44
/>
45
<InputGroupAddon>
46
<Clock2Icon class="text-muted-foreground" />
47
</InputGroupAddon>
48
</InputGroup>
49
</Field>
50
</FieldGroup>
51
</CardFooter>
52
</Card>
53
);
54
}

Booked Dates

January 2026
SuMoTuWeThFrSa
1
import { createSignal } from "solid-js";
2
import { Calendar } from "~/components/ui/calendar";
3
import { Card, CardContent } from "~/components/ui/card";
4
5
export default function CalendarBookedDates() {
6
const initialDate = new Date(new Date().getFullYear(), 0, 6);
7
const [date, setDate] = createSignal(initialDate);
8
const bookedDates = Array.from(
9
{ length: 15 },
10
(_, index) => new Date(new Date().getFullYear(), 0, 12 + index),
11
);
12
13
return (
14
<Card class="mx-auto w-fit p-0">
15
<CardContent class="p-0">
16
<Calendar
17
mode="single"
18
defaultMonth={date()}
19
selected={date()}
20
onSelect={setDate}
21
disabled={bookedDates}
22
modifiers={{ booked: bookedDates }}
23
modifiersClassNames={{ booked: "[&>button]:line-through opacity-100" }}
24
/>
25
</CardContent>
26
</Card>
27
);
28
}

Custom Cell Size

December2026December 2026
SuMoTuWeThFrSa
Custom\
1
import { addDays } from "date-fns";
2
import { createSignal, Show, splitProps } from "solid-js";
3
import {
4
Calendar,
5
CalendarDayButton,
6
type CalendarDayButtonProps,
7
type CalendarRangeValue,
8
} from "~/components/ui/calendar";
9
import { Card, CardContent } from "~/components/ui/card";
10
11
const PriceDayButton = (props: CalendarDayButtonProps) => {
12
const [local, others] = splitProps(props, ["children", "day", "modifiers"]);
13
const isWeekend = () => local.day.date.getDay() === 0 || local.day.date.getDay() === 6;
14
15
return (
16
<CalendarDayButton day={local.day} modifiers={local.modifiers} {...others}>
17
{local.children}
18
<Show when={!local.modifiers.outside}>
19
<span>{isWeekend() ? "$120" : "$100"}</span>
20
</Show>
21
</CalendarDayButton>
22
);
23
};
24
25
export default function CalendarCustomDays() {
26
const start = new Date(new Date().getFullYear(), 11, 8);
27
const [range, setRange] = createSignal<CalendarRangeValue>({
28
from: start,
29
to: addDays(start, 10),
30
});
31
32
return (
33
<Card class="mx-auto w-fit p-0">
34
<CardContent class="p-0">
35
<Calendar
36
mode="range"
37
defaultMonth={range().from}
38
selected={range()}
39
onSelect={setRange}
40
captionLayout="dropdown"
41
class="[--cell-size:--spacing(10)] md:[--cell-size:--spacing(12)]"
42
formatters={{
43
formatMonthDropdown: (date) => date.toLocaleString("default", { month: "long" }),
44
}}
45
components={{ DayButton: PriceDayButton }}
46
/>
47
</CardContent>
48
</Card>
49
);
50
}

Customize calendar cells with the --cell-size CSS variable:

1
<Calendar
2
mode="single"
3
selected={date()}
4
onSelect={setDate}
5
class="rounded-lg border [--cell-size:--spacing(11)] md:[--cell-size:--spacing(12)]"
6
/>

Or use fixed values:

1
<Calendar
2
mode="single"
3
selected={date()}
4
onSelect={setDate}
5
class="rounded-lg border [--cell-size:2.75rem] md:[--cell-size:3rem]"
6
/>

Week Numbers

Use showWeekNumber to show ISO week numbers.

January 2026
SuMoTuWeThFrSa
1
2
3
4
5
1
import { createSignal } from "solid-js";
2
import { Calendar } from "~/components/ui/calendar";
3
import { Card, CardContent } from "~/components/ui/card";
4
5
export default function CalendarWeekNumbers() {
6
const initialDate = new Date(new Date().getFullYear(), 0, 12);
7
const [date, setDate] = createSignal(initialDate);
8
9
return (
10
<Card class="mx-auto w-fit p-0">
11
<CardContent class="p-0">
12
<Calendar
13
mode="single"
14
defaultMonth={date()}
15
selected={date()}
16
onSelect={setDate}
17
showWeekNumber
18
/>
19
</CardContent>
20
</Card>
21
);
22
}

API Reference

Calendar

The Calendar wrapper follows the Corvu Calendar API reference and adds the DayPicker-compatible props shown below.

PropTypeDefaultDescription
mode"single" | "multiple" | "range"-Selection mode.
selectedDate | Date[] | { from: Date; to?: Date }-Controlled selected value.
onSelect(selected, triggerDate, modifiers, event) => void-Called after selection changes.
monthDate-Controlled month to display.
onMonthChange(month: Date) => void-Called when the displayed month changes.
captionLayout"label" | "dropdown" | "dropdown-months" | "dropdown-years""label"Caption presentation.
numberOfMonthsnumber1Number of visible months.
showOutsideDaysbooleantrueWhether adjacent-month days are visible.
showWeekNumberbooleanfalseWhether to show ISO week numbers.
disabledCalendarMatcher | CalendarMatcher[]-Dates that cannot be selected.
classstring-Additional classes on the root element.

For primitive props, keyboard behavior, and accessibility details, see the Corvu Calendar API reference.

On This Page

  • Installation
  • Usage
  • Date Picker
  • Persian Locale
  • Selected Date (With Time Zone)
  • Basic
  • Range Calendar
  • Month and Year Selector
  • Presets
  • Date and Time Picker
  • Booked Dates
  • Custom Cell Size
  • Week Numbers
  • API Reference
    • Calendar
Built by Kevin Abatan. The source code is available on GitHub.