Presets

Blocks

Get PRO

Need premium blocks and templates? Upgrade to PRO and get access.

Upgrade
JoinLogin
Presets
Monochrome
Overview
  • Introduction
  • Components
  • Installation
Components
  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Avatar
  • Badge
  • Breadcrumb
  • Button
  • Button Group
  • CalendarNEW
  • Card
  • CarouselNEW
  • Checkbox
  • Collapsible
  • Dialog
  • Dropdown Menu
  • Empty
  • Field
  • Input
  • Input Group
  • Item
  • Kbd
  • Label
  • Native Select
  • Scroll Area
  • SelectNEW
  • Separator
  • SwitchNEW
  • Table
  • Tabs
  • Textarea
  • TooltipNEW

Calendar

PreviousNext

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";4

Installation

pnpm dlx sprawlify@latest add calendar
npx sprawlify@latest add calendar
yarn sprawlify@latest add calendar
bunx --bun sprawlify@latest add calendar

Install the following dependencies:

pnpm add @sprawlify/primitives @sprawlify/solid
npm install @sprawlify/primitives @sprawlify/solid
yarn add @sprawlify/primitives @sprawlify/solid
bun add @sprawlify/primitives @sprawlify/solid

Add the following files to your project:

calendar.tsx
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};422

Update 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 CalendarViews

Get PRO

Need premium blocks and templates? Upgrade to PRO and get access.