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 * as React from "react";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/react
npm install @sprawlify/primitives @sprawlify/react
yarn add @sprawlify/primitives @sprawlify/react
bun add @sprawlify/primitives @sprawlify/react

Add the following files to your project:

calendar.tsx
1"use client";23import * as React from "react";4import { cn } from "@/lib/utils";5import { Button } from "@/components/ui/button";6import { DatePicker as DatePickerPrimitive } from "@sprawlify/react/date-picker";7import { type DateValue } from "@internationalized/date";8import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";910function CalendarViewHeader({11  buttonVariant = "ghost",12}: {13  buttonVariant?: React.ComponentProps<typeof Button>["variant"];14}) {15  return (16    <DatePickerPrimitive.ViewControl className="relative flex w-full items-center justify-between gap-1">17      <Button18        variant={buttonVariant}19        size="icon"20        className="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"21        asChild22      >23        <DatePickerPrimitive.PrevTrigger>24          <ChevronLeftIcon className="size-4" />25        </DatePickerPrimitive.PrevTrigger>26      </Button>2728      <DatePickerPrimitive.ViewTrigger className="flex h-(--cell-size) items-center justify-center text-sm font-medium select-none">29        <DatePickerPrimitive.RangeText />30      </DatePickerPrimitive.ViewTrigger>3132      <Button33        variant={buttonVariant}34        size="icon"35        className="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"36        asChild37      >38        <DatePickerPrimitive.NextTrigger>39          <ChevronRightIcon className="size-4" />40        </DatePickerPrimitive.NextTrigger>41      </Button>42    </DatePickerPrimitive.ViewControl>43  );44}4546function CalendarSelectHeader({47  buttonVariant = "ghost",48  className,49}: {50  buttonVariant?: React.ComponentProps<typeof Button>["variant"];51  className?: string;52}) {53  return (54    <DatePickerPrimitive.ViewControl55      className={cn("relative flex w-full items-center justify-between gap-1", className)}56    >57      <Button58        variant={buttonVariant}59        size="icon"60        className="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"61        asChild62      >63        <DatePickerPrimitive.PrevTrigger>64          <ChevronLeftIcon className="size-4" />65        </DatePickerPrimitive.PrevTrigger>66      </Button>6768      <div className="flex items-center gap-1">69        <span className="relative">70          <DatePickerPrimitive.MonthSelect className="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 className="pointer-events-none absolute top-1/2 right-1 size-3.5 -translate-y-1/2 text-muted-foreground" />72        </span>73        <span className="relative">74          <DatePickerPrimitive.YearSelect className="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 className="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        className="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"83        asChild84      >85        <DatePickerPrimitive.NextTrigger>86          <ChevronRightIcon className="size-4" />87        </DatePickerPrimitive.NextTrigger>88      </Button>89    </DatePickerPrimitive.ViewControl>90  );91}9293function CalendarDayView({94  showOutsideDays = true,95  header,96  cell,97}: {98  showOutsideDays?: boolean;99  header?: React.ReactNode;100  cell?: (day: DateValue) => React.ReactNode;101}) {102  return (103    <DatePickerPrimitive.View view="day" className="flex flex-col gap-4">104      <DatePickerPrimitive.Context>105        {(api) => (106          <>107            {header ?? <CalendarSelectHeader />}108            <CalendarDayTable109              weeks={api.weeks}110              weekDays={api.weekDays}111              focusedMonth={api.focusedValue.month}112              showOutsideDays={showOutsideDays}113              cell={cell}114            />115          </>116        )}117      </DatePickerPrimitive.Context>118    </DatePickerPrimitive.View>119  );120}121122function CalendarMonthView({123  header,124  cell,125}: {126  header?: React.ReactNode;127  cell?: (month: { label: string; value: number }) => React.ReactNode;128}) {129  return (130    <DatePickerPrimitive.View view="month" className="flex flex-col gap-4">131      <DatePickerPrimitive.Context>132        {(api) => (133          <>134            {header ?? <CalendarSelectHeader />}135            <DatePickerPrimitive.Table className="w-full border-collapse">136              <DatePickerPrimitive.TableBody>137                {api.getMonthsGrid({ columns: 4, format: "short" }).map((months, rowIndex) => (138                  <DatePickerPrimitive.TableRow key={rowIndex} className="mt-2 flex w-full">139                    {months.map((month, colIndex) => (140                      <DatePickerPrimitive.TableCell141                        key={colIndex}142                        value={month.value}143                        className="flex-1 p-0 text-center"144                      >145                        <DatePickerPrimitive.TableCellTrigger asChild>146                          <Button variant="ghost" className="w-full text-sm font-normal">147                            {cell ? cell(month) : month.label}148                          </Button>149                        </DatePickerPrimitive.TableCellTrigger>150                      </DatePickerPrimitive.TableCell>151                    ))}152                  </DatePickerPrimitive.TableRow>153                ))}154              </DatePickerPrimitive.TableBody>155            </DatePickerPrimitive.Table>156          </>157        )}158      </DatePickerPrimitive.Context>159    </DatePickerPrimitive.View>160  );161}162163function CalendarYearView({164  header,165  cell,166}: {167  header?: React.ReactNode;168  cell?: (year: { label: string; value: number }) => React.ReactNode;169}) {170  return (171    <DatePickerPrimitive.View view="year" className="flex flex-col gap-4">172      <DatePickerPrimitive.Context>173        {(api) => (174          <>175            {header ?? <CalendarSelectHeader />}176            <DatePickerPrimitive.Table className="w-full border-collapse">177              <DatePickerPrimitive.TableBody>178                {api.getYearsGrid({ columns: 4 }).map((years, rowIndex) => (179                  <DatePickerPrimitive.TableRow key={rowIndex} className="mt-2 flex w-full">180                    {years.map((year, colIndex) => (181                      <DatePickerPrimitive.TableCell182                        key={colIndex}183                        value={year.value}184                        className="flex-1 p-0 text-center"185                      >186                        <DatePickerPrimitive.TableCellTrigger asChild>187                          <Button variant="ghost" className="w-full text-sm font-normal">188                            {cell ? cell(year) : year.label}189                          </Button>190                        </DatePickerPrimitive.TableCellTrigger>191                      </DatePickerPrimitive.TableCell>192                    ))}193                  </DatePickerPrimitive.TableRow>194                ))}195              </DatePickerPrimitive.TableBody>196            </DatePickerPrimitive.Table>197          </>198        )}199      </DatePickerPrimitive.Context>200    </DatePickerPrimitive.View>201  );202}203204function CalendarDayButton({205  children,206  className,207  ...props208}: React.ComponentProps<typeof DatePickerPrimitive.TableCellTrigger>) {209  return (210    <DatePickerPrimitive.TableCellTrigger211      className={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        className,224      )}225      {...props}226    >227      {children}228    </DatePickerPrimitive.TableCellTrigger>229  );230}231232function CalendarDayTable({233  weeks,234  weekDays,235  focusedMonth,236  showOutsideDays = true,237  visibleRange,238  cell,239}: {240  weeks: DateValue[][];241  weekDays: { short: string }[];242  focusedMonth: number;243  showOutsideDays?: boolean;244  visibleRange?: { start: DateValue; end: DateValue };245  cell?: (day: DateValue) => React.ReactNode;246}) {247  return (248    <DatePickerPrimitive.Table className="w-full border-collapse">249      <DatePickerPrimitive.TableHead>250        <DatePickerPrimitive.TableRow className="flex">251          {weekDays.map((weekDay, i) => (252            <DatePickerPrimitive.TableHeader253              key={i}254              className="flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none"255            >256              {weekDay.short}257            </DatePickerPrimitive.TableHeader>258          ))}259        </DatePickerPrimitive.TableRow>260      </DatePickerPrimitive.TableHead>261      <DatePickerPrimitive.TableBody>262        {weeks.map((week, weekIndex) => (263          <DatePickerPrimitive.TableRow key={weekIndex} className="mt-2 flex w-full">264            {week.map((day, dayIndex) => {265              const isOutside = day.month !== focusedMonth;266              if (!showOutsideDays && isOutside) {267                return <td key={dayIndex} className="flex-1 p-0" aria-hidden />;268              }269              return (270                <DatePickerPrimitive.TableCell271                  key={dayIndex}272                  value={day}273                  visibleRange={visibleRange}274                  className={cn(275                    "group/day relative aspect-square h-full w-full flex-1 rounded-(--cell-radius) p-0 text-center select-none",276                    "[&:last-child[data-selected]_div]:rounded-r-(--cell-radius)",277                    "[&:first-child[data-selected]_div]:rounded-l-(--cell-radius)",278                  )}279                >280                  <CalendarDayButton>{cell ? cell(day) : day.day}</CalendarDayButton>281                </DatePickerPrimitive.TableCell>282              );283            })}284          </DatePickerPrimitive.TableRow>285        ))}286      </DatePickerPrimitive.TableBody>287    </DatePickerPrimitive.Table>288  );289}290291function CalendarDualMonthDayView({292  showOutsideDays = true,293  cell,294}: {295  showOutsideDays?: boolean;296  cell?: (day: DateValue) => React.ReactNode;297}) {298  return (299    <DatePickerPrimitive.View view="day" className="flex flex-col gap-4">300      <DatePickerPrimitive.Context>301        {(api) => {302          const offset = api.getOffset({ months: 1 });303          return (304            <div className="flex gap-4 flex-row">305              {/* First month */}306              <div className="flex flex-col gap-4">307                <div className="relative flex w-full items-center justify-between gap-1">308                  <DatePickerPrimitive.PrevTrigger asChild>309                    <Button310                      variant="ghost"311                      size="icon"312                      className="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"313                    >314                      <ChevronLeftIcon className="size-4" />315                    </Button>316                  </DatePickerPrimitive.PrevTrigger>317                  <span className="text-sm font-medium select-none">318                    {api.visibleRangeText.start}319                  </span>320                  <div className="size-(--cell-size)" />321                </div>322                <CalendarDayTable323                  weeks={api.weeks}324                  weekDays={api.weekDays}325                  focusedMonth={api.focusedValue.month}326                  showOutsideDays={showOutsideDays}327                  cell={cell}328                />329              </div>330              {/* Second month */}331              <div className="flex flex-col gap-4">332                <div className="relative flex w-full items-center justify-between gap-1">333                  <div className="size-(--cell-size)" />334                  <span className="text-sm font-medium select-none">335                    {api.visibleRangeText.end}336                  </span>337                  <DatePickerPrimitive.NextTrigger asChild>338                    <Button339                      variant="ghost"340                      size="icon"341                      className="size-(--cell-size) p-0 select-none aria-disabled:opacity-50"342                    >343                      <ChevronRightIcon className="size-4" />344                    </Button>345                  </DatePickerPrimitive.NextTrigger>346                </div>347                <CalendarDayTable348                  weeks={offset.weeks}349                  weekDays={api.weekDays}350                  focusedMonth={offset.visibleRange.start.month}351                  showOutsideDays={showOutsideDays}352                  visibleRange={offset.visibleRange}353                  cell={cell}354                />355              </div>356            </div>357          );358        }}359      </DatePickerPrimitive.Context>360    </DatePickerPrimitive.View>361  );362}363364function Calendar({365  className,366  showOutsideDays = true,367  children,368  numOfMonths,369  ...props370}: Omit<React.ComponentProps<typeof DatePickerPrimitive.Root>, "inline"> & {371  className?: string;372  showOutsideDays?: boolean;373  children?: React.ReactNode;374}) {375  const isDualMonth = numOfMonths && numOfMonths >= 2;376377  return (378    <DatePickerPrimitive.Root inline numOfMonths={numOfMonths} {...props}>379      <div380        data-slot="calendar"381        className={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          className,384        )}385      >386        {children || (387          <>388            {isDualMonth ? (389              <CalendarDualMonthDayView showOutsideDays={showOutsideDays} />390            ) : (391              <CalendarDayView showOutsideDays={showOutsideDays} />392            )}393            <CalendarMonthView />394            <CalendarYearView />395          </>396        )}397      </div>398    </DatePickerPrimitive.Root>399  );400}401402const CalendarPresetTrigger = DatePickerPrimitive.PresetTrigger;403404export {405  Calendar,406  CalendarDayView,407  CalendarDualMonthDayView,408  CalendarDayTable,409  CalendarMonthView,410  CalendarYearView,411  CalendarViewHeader,412  CalendarSelectHeader,413  CalendarDayButton,414  CalendarPresetTrigger,415};416

Update the import paths to match your project setup.

Usage

1import { Calendar } from "@/components/ui/calendar"
1const [date, setDate] = React.useState<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    className="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" className="rounded-lg border" />;

Calendar Booked Dates

1import * as React from "react";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 * as React from "react";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 * as React from "react";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.