Calendar

A month calendar for choosing a day or a range of days. It shows one, two or three months, can swap its caption for a month and a year menu, and draws its days square or round. On an Arabic page it shows Arabic month names with Western digits, starts the week on Saturday and mirrors.

Installation

bash
npx @smicolon/fasla-ui@latest add calendar

Run init once per project before adding components. See Installation.

Preview

June 2026

States

The days the calendar draws: plain, outside the month in muted text, today on a soft fill, and the chosen day on a solid one. A chosen today keeps a dot under its number. Days you turn off with disabled show at half opacity and cannot be chosen. Point at a day to see its hover.

June 2026
June 2026

Radius

radius="standard" gives the days and the range band 8px corners, and radius="rounded" makes them full circles. The calendar's card keeps its 8px corners either way.

June 2026
June 2026

Range

mode="range" chooses a start and an end. Both ends are drawn as chosen days, and the days between sit on a band that breaks at the end of each week. Set numberOfMonths to 2 to show two months side by side; only the outer months carry arrows.

June 2026
July 2026

Three months

Up to three months sit side by side, for a range that runs across them. Where the page is too narrow for them, the months wrap onto more rows.

June 2026
July 2026
August 2026

Month and year menus

captionLayout="dropdown" replaces the month's name and its arrows with two Select menus, one for the month and one for the year. startMonth and endMonth set the years the menu lists.

June 2026

With time slots

A composition rather than a prop: a column of time slots, headed by the chosen day, sits at the calendar's start, divided from it by a line. Slots that have passed are muted and cannot be chosen. The code is under Usage.

Thursday, 18

June 2026

With presets

Also a composition: Ghost Buttons over the calendar choose a day in one press and move the calendar to its month. Here today is the 6th, so choosing Today shows the dot. The code is under Usage.

June 2026

In a form

In a form, use the calendar through Date Picker: a field with a label and helper text that opens the calendar under it and submits the chosen day with the form.

Date of birth
Your date of birth is used to calculate your age.

Accessibility

  • Each month is a grid of buttons, one per day, each named with its full date and whether it is today or chosen. The caption names the grid.
  • The arrow keys move a day or a week, Page Up and Page Down a month, and with Shift a year. Home and End go to the start and end of the week, and Enter or Space chooses the day.
  • Only one day is in the tab order: the chosen day, or else today. Tab reaches the arrows first, then that day.
  • Direction and language come from the page, never a prop. On an Arabic page the arrows swap ends and point the other way, the arrow keys follow the reading direction, and every label is read out in Arabic.

Props

Calendar

PropTypeDefaultDescription
mode"single" | "multiple" | "range"—How days are chosen: one, several, or a range.
selectedDate | Date[] | DateRange—The chosen day, days or range. Use it with onSelect.
onSelect(selected) => void—Called with the new selection when a day is pressed.
requiredbooleanfalseKeeps a day chosen: pressing the chosen day again does not clear it.
defaultMonthDate—The month shown on first render.
monthDate—The month shown, when you control it. Use it with onMonthChange.
onMonthChange(month: Date) => void—Called with the new month when the arrows or the menus move it.
numberOfMonthsnumber1How many months show side by side: 1, 2 or 3.
captionLayout"label" | "dropdown""label"The month's name with arrows, or a month and a year menu.
startMonthDate—The earliest month the arrows and menus reach.
endMonthDate—The latest month the arrows and menus reach.
radius"standard" | "rounded""standard"Day and range corners: 8px, or full circles.
disabledMatcher | Matcher[]—Days that cannot be chosen: a date, a range, a weekday, or a function.
todayDate—The day drawn as today. Defaults to the real date.
showOutsideDaysbooleantrueShows the days of the months either side, muted.
fixedWeeksbooleantrueAlways shows six weeks, so the calendar keeps its height between months.
weekStartsOn0 | 1 | 2 | 3 | 4 | 5 | 6—The first day of the week, 0 for Sunday to 6 for Saturday. Defaults to Saturday on a right-to-left page and Sunday otherwise.
localeDayPickerLocale—The month and weekday names and the labels. Defaults to Arabic on an Arabic page and English otherwise.
classNamestring—Extra CSS classes for the calendar's card.

Usage

One day

tsx
"use client"

import * as React from "react"
import { Calendar } from "@/components/ui/calendar"

export function DeliveryDay() {
  const [day, setDay] = React.useState<Date | undefined>(new Date())
  return <Calendar mode="single" selected={day} onSelect={setDay} />
}

A range over two months

tsx
"use client"

import * as React from "react"
import type { DateRange } from "react-day-picker"
import { Calendar } from "@/components/ui/calendar"

export function StayDates() {
  const [stay, setStay] = React.useState<DateRange | undefined>()
  return (
    <Calendar
      mode="range"
      numberOfMonths={2}
      selected={stay}
      onSelect={setStay}
      disabled={{ before: new Date() }}
    />
  )
}

With time slots

tsx
"use client"

import * as React from "react"
import { Calendar } from "@/components/ui/calendar"
import { cn } from "@/lib/utils"

const slots = ["09:00", "09:30", "10:00", "10:30", "11:00", "11:30"]

export function Appointment() {
  const [day, setDay] = React.useState<Date>(new Date())
  const [time, setTime] = React.useState<string>()
  return (
    <div className="flex w-fit rounded-md bg-background p-3 shadow">
      <div className="relative w-[9.25rem] shrink-0 border-e">
        <div className="absolute inset-0 flex flex-col gap-4 px-3">
          <p className="pt-1 text-center text-sm font-medium">
            {day.toLocaleDateString(undefined, { weekday: "long", day: "numeric" })}
          </p>
          <div className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto">
            {slots.map((slot) => (
              <button
                key={slot}
                type="button"
                aria-pressed={slot === time}
                onClick={() => setTime(slot)}
                className={cn(
                  "h-8 shrink-0 rounded-md border text-sm",
                  slot === time ? "border-primary bg-primary text-primary-foreground" : "border-input hover:bg-accent"
                )}
              >
                {slot}
              </button>
            ))}
          </div>
        </div>
      </div>
      <Calendar
        mode="single"
        required
        selected={day}
        onSelect={setDay}
        className="ms-2 rounded-none p-0 shadow-none"
      />
    </div>
  )
}

With presets

tsx
"use client"

import * as React from "react"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"

const shift = (date: Date, days: number, months = 0) =>
  new Date(date.getFullYear(), date.getMonth() + months, date.getDate() + days)
const startOfMonth = (date: Date) => new Date(date.getFullYear(), date.getMonth(), 1)

export function DayWithPresets() {
  const today = new Date()
  const [day, setDay] = React.useState<Date>(today)
  const [month, setMonth] = React.useState<Date>(startOfMonth(today))
  const presets = [
    { label: "Today", date: today },
    { label: "Yesterday", date: shift(today, -1) },
    { label: "Last week", date: shift(today, -7) },
    { label: "Last month", date: shift(today, 0, -1) },
  ]
  return (
    <div className="flex w-fit flex-col items-center gap-6 rounded-md bg-background p-3 shadow">
      <div className="flex gap-1">
        {presets.map((preset) => (
          <Button
            key={preset.label}
            variant="ghost"
            size="xs"
            onClick={() => {
              setDay(preset.date)
              setMonth(startOfMonth(preset.date))
            }}
          >
            {preset.label}
          </Button>
        ))}
      </div>
      <Calendar
        mode="single"
        required
        month={month}
        onMonthChange={setMonth}
        selected={day}
        onSelect={setDay}
        className="rounded-none p-0 shadow-none"
      />
    </div>
  )
}

Month and year menus

tsx
"use client"

import * as React from "react"
import { Calendar } from "@/components/ui/calendar"

export function BirthDay() {
  const [day, setDay] = React.useState<Date | undefined>()
  return (
    <Calendar
      mode="single"
      captionLayout="dropdown"
      startMonth={new Date(1926, 0)}
      endMonth={new Date()}
      selected={day}
      onSelect={setDay}
    />
  )
}