A calendar component that allows users to select a date or a range of dates.
1import { createSignal } from "solid-js";2import { Calendar } from "@/components/ui/calendar";3import { CalendarDate, type DateValue } from "@internationalized/date";4Installation
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/solidnpm install @sprawlify/primitives @sprawlify/solidyarn add @sprawlify/primitives @sprawlify/solidbun add @sprawlify/primitives @sprawlify/solid
Add the following files to your project:
1import { cn } from "@/lib/utils";2import { Button } from "@/components/ui/button";3import { DatePicker as DatePickerPrimitive } from "@sprawlify/solid/date-picker";4import type { DateValue } from "@internationalized/date";5import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-solid";6import { splitProps, type ComponentProps, type JSX, Show, For } from "solid-js";78function CalendarViewHeader(props: { buttonVariant?: ComponentProps<typeof Button>["variant"] }) {9 const [local] = splitProps(props, ["buttonVariant"]);1011 const buttonVariant = () => local.buttonVariant || "ghost";1213 return (14 <DatePickerPrimitive.ViewControl class="relative flex w-full items-center justify-between gap-1">15 <Button16 variant={buttonVariant()}17 size="icon"18 class="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"19 asChild={(props) => (20 <DatePickerPrimitive.PrevTrigger {...props()}>21 <ChevronLeftIcon class="size-4" />22 </DatePickerPrimitive.PrevTrigger>23 )}24 />2526 <DatePickerPrimitive.ViewTrigger class="flex h-(--cell-size) items-center justify-center text-sm font-medium select-none">27 <DatePickerPrimitive.RangeText />28 </DatePickerPrimitive.ViewTrigger>2930 <Button31 variant={buttonVariant()}32 size="icon"33 class="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"34 asChild={(props) => (35 <DatePickerPrimitive.NextTrigger {...props()}>36 <ChevronRightIcon class="size-4" />37 </DatePickerPrimitive.NextTrigger>38 )}39 />40 </DatePickerPrimitive.ViewControl>41 );42}4344function CalendarSelectHeader(props: {45 buttonVariant?: ComponentProps<typeof Button>["variant"];46 class?: string;47}) {48 const [local, others] = splitProps(props, ["buttonVariant", "class"]);4950 const buttonVariant = () => local.buttonVariant || "ghost";5152 return (53 <DatePickerPrimitive.ViewControl54 class={cn("relative flex w-full items-center justify-between gap-1", local.class)}55 {...others}56 >57 <Button58 variant={buttonVariant()}59 size="icon"60 class="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"61 asChild={(props) => (62 <DatePickerPrimitive.PrevTrigger {...props()}>63 <ChevronLeftIcon class="size-4" />64 </DatePickerPrimitive.PrevTrigger>65 )}66 />6768 <div class="flex items-center gap-1">69 <span class="relative">70 <DatePickerPrimitive.MonthSelect 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" />71 <ChevronDownIcon class="pointer-events-none absolute top-1/2 right-1 size-3.5 -translate-y-1/2 text-muted-foreground" />72 </span>73 <span class="relative">74 <DatePickerPrimitive.YearSelect 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" />75 <ChevronDownIcon class="pointer-events-none absolute top-1/2 right-1 size-3.5 -translate-y-1/2 text-muted-foreground" />76 </span>77 </div>7879 <Button80 variant={buttonVariant()}81 size="icon"82 class="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"83 asChild={(props) => (84 <DatePickerPrimitive.NextTrigger {...props()}>85 <ChevronRightIcon class="size-4" />86 </DatePickerPrimitive.NextTrigger>87 )}88 />89 </DatePickerPrimitive.ViewControl>90 );91}9293function CalendarDayView(props: {94 showOutsideDays?: boolean;95 header?: JSX.Element;96 cell?: (day: DateValue) => JSX.Element;97}) {98 return (99 <DatePickerPrimitive.View view="day" class="flex flex-col gap-4">100 <DatePickerPrimitive.Context>101 {(api) => (102 <>103 <Show when={props.header} fallback={<CalendarSelectHeader />}>104 {props.header}105 </Show>106 <CalendarDayTable107 weeks={api().weeks}108 weekDays={api().weekDays}109 focusedMonth={api().focusedValue.month}110 showOutsideDays={props.showOutsideDays ?? true}111 cell={props.cell}112 />113 </>114 )}115 </DatePickerPrimitive.Context>116 </DatePickerPrimitive.View>117 );118}119120function CalendarMonthView(props: {121 header?: JSX.Element;122 cell?: (month: { label: string; value: number }) => JSX.Element;123}) {124 return (125 <DatePickerPrimitive.View view="month" class="flex flex-col gap-4">126 <DatePickerPrimitive.Context>127 {(api) => (128 <>129 <Show when={props.header} fallback={<CalendarSelectHeader />}>130 {props.header}131 </Show>132 <DatePickerPrimitive.Table class="w-full border-collapse">133 <DatePickerPrimitive.TableBody>134 <For each={api().getMonthsGrid({ columns: 4, format: "short" })}>135 {(months) => (136 <DatePickerPrimitive.TableRow class="mt-2 flex w-full">137 <For each={months}>138 {(month) => (139 <DatePickerPrimitive.TableCell140 value={month.value}141 class="flex-1 p-0 text-center"142 >143 <DatePickerPrimitive.TableCellTrigger>144 <Button variant="ghost" class="w-full text-sm font-normal">145 {props.cell ? props.cell(month) : month.label}146 </Button>147 </DatePickerPrimitive.TableCellTrigger>148 </DatePickerPrimitive.TableCell>149 )}150 </For>151 </DatePickerPrimitive.TableRow>152 )}153 </For>154 </DatePickerPrimitive.TableBody>155 </DatePickerPrimitive.Table>156 </>157 )}158 </DatePickerPrimitive.Context>159 </DatePickerPrimitive.View>160 );161}162163function CalendarYearView(props: {164 header?: JSX.Element;165 cell?: (year: { label: string; value: number }) => JSX.Element;166}) {167 return (168 <DatePickerPrimitive.View view="year" class="flex flex-col gap-4">169 <DatePickerPrimitive.Context>170 {(api) => (171 <>172 <Show when={props.header} fallback={<CalendarSelectHeader />}>173 {props.header}174 </Show>175 <DatePickerPrimitive.Table class="w-full border-collapse">176 <DatePickerPrimitive.TableBody>177 <For each={api().getYearsGrid({ columns: 4 })}>178 {(years) => (179 <DatePickerPrimitive.TableRow class="mt-2 flex w-full">180 <For each={years}>181 {(year) => (182 <DatePickerPrimitive.TableCell183 value={year.value}184 class="flex-1 p-0 text-center"185 >186 <DatePickerPrimitive.TableCellTrigger>187 <Button variant="ghost" class="w-full text-sm font-normal">188 {props.cell ? props.cell(year) : year.label}189 </Button>190 </DatePickerPrimitive.TableCellTrigger>191 </DatePickerPrimitive.TableCell>192 )}193 </For>194 </DatePickerPrimitive.TableRow>195 )}196 </For>197 </DatePickerPrimitive.TableBody>198 </DatePickerPrimitive.Table>199 </>200 )}201 </DatePickerPrimitive.Context>202 </DatePickerPrimitive.View>203 );204}205206function CalendarDayButton(props: ComponentProps<typeof DatePickerPrimitive.TableCellTrigger>) {207 const [local, others] = splitProps(props, ["class", "children"]);208209 return (210 <DatePickerPrimitive.TableCellTrigger211 class={cn(212 "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",213 "hover:bg-accent hover:text-accent-foreground",214 "data-[selected]:bg-primary data-[selected]:text-primary-foreground",215 "data-[today]:bg-accent data-[today]:text-accent-foreground",216 "data-[outside-range]:text-muted-foreground/50",217 "data-[disabled]:text-muted-foreground data-[disabled]:opacity-50",218 "data-[unavailable]:text-muted-foreground data-[unavailable]:line-through data-[unavailable]:opacity-40",219 "data-[in-range]:rounded-none data-[in-range]:bg-accent data-[in-range]:text-accent-foreground",220 "data-[range-start]:rounded-l-(--cell-radius) data-[range-start]:bg-primary data-[range-start]:text-primary-foreground",221 "data-[range-end]:rounded-r-(--cell-radius) data-[range-end]:bg-primary data-[range-end]:text-primary-foreground",222 "focus-visible:relative focus-visible:z-10 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1",223 local.class,224 )}225 {...others}226 >227 {local.children}228 </DatePickerPrimitive.TableCellTrigger>229 );230}231232function CalendarDayTable(props: {233 weeks: DateValue[][];234 weekDays: { short: string }[];235 focusedMonth: number;236 showOutsideDays?: boolean;237 visibleRange?: { start: DateValue; end: DateValue };238 cell?: (day: DateValue) => JSX.Element;239}) {240 return (241 <DatePickerPrimitive.Table class="w-full border-collapse">242 <DatePickerPrimitive.TableHead>243 <DatePickerPrimitive.TableRow class="flex">244 <For each={props.weekDays}>245 {(weekDay) => (246 <DatePickerPrimitive.TableHeader class="flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none">247 {weekDay.short}248 </DatePickerPrimitive.TableHeader>249 )}250 </For>251 </DatePickerPrimitive.TableRow>252 </DatePickerPrimitive.TableHead>253 <DatePickerPrimitive.TableBody>254 <For each={props.weeks}>255 {(week) => (256 <DatePickerPrimitive.TableRow class="mt-2 flex w-full">257 <For each={week}>258 {(day) => {259 const isOutside = day.month !== props.focusedMonth;260 const showOutsideDays = props.showOutsideDays ?? true;261262 if (!showOutsideDays && isOutside) {263 return <td class="flex-1 p-0" aria-hidden />;264 }265266 return (267 <DatePickerPrimitive.TableCell268 value={day}269 visibleRange={props.visibleRange}270 class={cn(271 "group/day relative aspect-square h-full w-full flex-1 rounded-(--cell-radius) p-0 text-center select-none",272 "[&:last-child[data-selected]_div]:rounded-r-(--cell-radius)",273 "[&:first-child[data-selected]_div]:rounded-l-(--cell-radius)",274 )}275 >276 <CalendarDayButton>277 {props.cell ? props.cell(day) : day.day}278 </CalendarDayButton>279 </DatePickerPrimitive.TableCell>280 );281 }}282 </For>283 </DatePickerPrimitive.TableRow>284 )}285 </For>286 </DatePickerPrimitive.TableBody>287 </DatePickerPrimitive.Table>288 );289}290291function CalendarDualMonthDayView(props: {292 showOutsideDays?: boolean;293 cell?: (day: DateValue) => JSX.Element;294}) {295 return (296 <DatePickerPrimitive.View view="day" class="flex flex-col gap-4">297 <DatePickerPrimitive.Context>298 {(api) => {299 const offset = api().getOffset({ months: 1 });300 return (301 <div class="flex gap-4 flex-row">302 {/* First month */}303 <div class="flex flex-col gap-4">304 <div class="relative flex w-full items-center justify-between gap-1">305 <DatePickerPrimitive.PrevTrigger>306 <Button307 variant="ghost"308 size="icon"309 class="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"310 >311 <ChevronLeftIcon class="size-4" />312 </Button>313 </DatePickerPrimitive.PrevTrigger>314 <span class="text-sm font-medium select-none">315 {api().visibleRangeText.start}316 </span>317 <div class="size-(--cell-size)" />318 </div>319 <CalendarDayTable320 weeks={api().weeks}321 weekDays={api().weekDays}322 focusedMonth={api().focusedValue.month}323 showOutsideDays={props.showOutsideDays}324 cell={props.cell}325 />326 </div>327 {/* Second month */}328 <div class="flex flex-col gap-4">329 <div class="relative flex w-full items-center justify-between gap-1">330 <div class="size-(--cell-size)" />331 <span class="text-sm font-medium select-none">{api().visibleRangeText.end}</span>332 <DatePickerPrimitive.NextTrigger>333 <Button334 variant="ghost"335 size="icon"336 class="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"337 >338 <ChevronRightIcon class="size-4" />339 </Button>340 </DatePickerPrimitive.NextTrigger>341 </div>342 <CalendarDayTable343 weeks={offset.weeks}344 weekDays={api().weekDays}345 focusedMonth={offset.visibleRange.start.month}346 showOutsideDays={props.showOutsideDays}347 visibleRange={offset.visibleRange}348 cell={props.cell}349 />350 </div>351 </div>352 );353 }}354 </DatePickerPrimitive.Context>355 </DatePickerPrimitive.View>356 );357}358359function Calendar(360 props: Omit<ComponentProps<typeof DatePickerPrimitive.Root>, "inline"> & {361 class?: string;362 showOutsideDays?: boolean;363 children?: JSX.Element;364 numOfMonths?: number;365 },366) {367 const [local, others] = splitProps(props, [368 "class",369 "showOutsideDays",370 "children",371 "numOfMonths",372 ]);373374 const isDualMonth = () => local.numOfMonths && local.numOfMonths >= 2;375 const showOutsideDays = () => local.showOutsideDays ?? true;376377 return (378 <DatePickerPrimitive.Root inline numOfMonths={local.numOfMonths} {...others}>379 <div380 data-slot="calendar"381 class={cn(382 "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",383 local.class,384 )}385 >386 <Show387 when={local.children}388 fallback={389 <>390 <Show391 when={isDualMonth()}392 fallback={<CalendarDayView showOutsideDays={showOutsideDays()} />}393 >394 <CalendarDualMonthDayView showOutsideDays={showOutsideDays()} />395 </Show>396 <CalendarMonthView />397 <CalendarYearView />398 </>399 }400 >401 {local.children}402 </Show>403 </div>404 </DatePickerPrimitive.Root>405 );406}407408const CalendarPresetTrigger = DatePickerPrimitive.PresetTrigger;409410export {411 Calendar,412 CalendarDayView,413 CalendarDualMonthDayView,414 CalendarDayTable,415 CalendarMonthView,416 CalendarYearView,417 CalendarViewHeader,418 CalendarSelectHeader,419 CalendarDayButton,420 CalendarPresetTrigger,421};422Update the import paths to match your project setup.
Usage
1import { Calendar } from "@/components/ui/calendar"1const [date, setDate] = createSignal<DateValue[]>([
2 new CalendarDate(2026, 3, 21),
3])
4
5return (
6 <Calendar
7 selectionMode="single"
8 value={date()}
9 onValueChange={(details) =>
10 setDate(details.value)
11 }
12 class="rounded-lg border"
13 />
14)Examples
Basic
A basic calendar component. We used className="rounded-lg border" to style the calendar.
1import { Calendar } from "@/components/ui/calendar";23export default function Preview() {4 return <Calendar selectionMode="single" class="rounded-lg border" />;Calendar Booked Dates
1import { createSignal } from "solid-js";2import { Calendar } from "@/components/ui/calendar";3import { Card, CardContent } from "@/components/ui/card";4import { CalendarDate, type DateValue } from "@internationalized/date";Date And Time Picker
1import { createSignal } from "solid-js";2import { Calendar } from "@/components/ui/calendar";3import { Card, CardContent, CardFooter } from "@/components/ui/card";4import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";Presets
1import { Button } from "@/components/ui/button";2import { Calendar, CalendarDayView, CalendarPresetTrigger } from "@/components/ui/calendar";3import { Card, CardContent, CardFooter } from "@/components/ui/card";4import { CalendarDate } from "@internationalized/date";Range Calendar
Use the selectionMode="range" prop to enable range selection.
1import { createSignal } from "solid-js";2import { Calendar } from "@/components/ui/calendar";3import { Card, CardContent } from "@/components/ui/card";4import { CalendarDate, type DateValue } from "@internationalized/date";Views
1import {2 Calendar,3 CalendarDayView,4 CalendarMonthView,On This Page
InstallationUsageExamplesBasicCalendar Booked DatesDate And Time PickerPresetsRange CalendarViewsGet PRO
Need premium blocks and templates? Upgrade to PRO and get access.