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
npx @smicolon/fasla-ui@latest add calendarRun init once per project before adding components. See Installation.
Preview
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
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.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
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.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
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.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
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.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
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.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
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
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
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.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
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.
Accessibility
- Each month is a
gridof 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
| Prop | Type | Default | Description |
|---|---|---|---|
mode | "single" | "multiple" | "range" | — | How days are chosen: one, several, or a range. |
selected | Date | 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. |
required | boolean | false | Keeps a day chosen: pressing the chosen day again does not clear it. |
defaultMonth | Date | — | The month shown on first render. |
month | Date | — | 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. |
numberOfMonths | number | 1 | How 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. |
startMonth | Date | — | The earliest month the arrows and menus reach. |
endMonth | Date | — | The latest month the arrows and menus reach. |
radius | "standard" | "rounded" | "standard" | Day and range corners: 8px, or full circles. |
disabled | Matcher | Matcher[] | — | Days that cannot be chosen: a date, a range, a weekday, or a function. |
today | Date | — | The day drawn as today. Defaults to the real date. |
showOutsideDays | boolean | true | Shows the days of the months either side, muted. |
fixedWeeks | boolean | true | Always shows six weeks, so the calendar keeps its height between months. |
weekStartsOn | 0 | 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. |
locale | DayPickerLocale | — | The month and weekday names and the labels. Defaults to Arabic on an Arabic page and English otherwise. |
className | string | — | Extra CSS classes for the calendar's card. |
Usage
One day
"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
"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
"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
"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
"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}
/>
)
}