Calendar
A month to pick a day or a range from: a booking's date, a stylist's time off. Round days on the button scale, the selected one black, weeks from Monday. Built on the vendored react-day-picker calendar.
Playground
Examples are pinned to October 2026, with today on Tuesday the 13th.
import { Calendar } from "@/components/shared/calendar";
<Calendar
mode="single"
selected={date}
onSelect={setDate}
disabled={[{ before: today }, { dayOfWeek: [0] }]}
/>Picking a day
The selected day is a black disc with white numerals. Today sits on a faint ink disc in a heavier weight, so it never competes with the selection. Days of the months either side stay visible and quiet, and can be picked.
import { Calendar } from "@/components/shared/calendar";
const [date, setDate] = useState<Date>();
<Calendar mode="single" selected={date} onSelect={setDate} />Sizes
Days follow the button scale: md 36px by default, a comfortable touch target in a popover. sm is 32px, for a narrow side panel. On a touch screen each day's hit area reaches up and down into the gap between weeks, to the 40px touch floor.
import { Calendar } from "@/components/shared/calendar";
<Calendar mode="single" />
<Calendar mode="single" size="sm" />Availability
Days a client can't book are disabled: they dim and can't be focused or picked. Here, the past, Sundays when the salon is closed, and Friday the 16th, fully booked.
import { Calendar } from "@/components/shared/calendar";
<Calendar
mode="single"
selected={date}
onSelect={setDate}
disabled={[{ before: today }, { dayOfWeek: [0] }, ...fullyBooked]}
/>A range
mode="range" picks a start and an end, like a stylist's week off. The ends are black; the days between share a quiet ink band.
import { Calendar } from "@/components/shared/calendar";
import type { DateRange } from "react-day-picker";
const [range, setRange] = useState<DateRange>();
<Calendar mode="range" selected={range} onSelect={setRange} />In a popover
The dashboard's date picker: a secondary button that names the day opens the month in a flush popover, and picking a day closes it. Press it.
import { Calendar } from "@/components/shared/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/shared/popover";
import { Button } from "@/components/shared/button";
import { CalendarDaysIcon } from "@hugeicons/core-free-icons";
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger render={<Button variant="secondary" leadingIcon={CalendarDaysIcon} />}>
{dayLabel(date)}
</PopoverTrigger>
<PopoverContent flush>
<Calendar
mode="single"
required
selected={date}
defaultMonth={date}
onSelect={(next) => { setDate(next); setOpen(false); }}
/>
</PopoverContent>
</Popover>Language
Pass the page's locale from react-day-picker/locale so month and weekday names match the rest of the page. Without it they stay in English.
import { Calendar } from "@/components/shared/calendar";
import { cs, enGB, sk } from "react-day-picker/locale";
<Calendar mode="single" locale={sk} selected={date} onSelect={setDate} />API reference
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "md" | The day's size: 32 or 36px, on the button scale. |
| mode | "single" | "multiple" | "range" | — | What a click selects. Without a mode the month is read-only. |
| selected | Date | Date[] | DateRange | — | The selection, matching the mode. Without onSelect it is only the starting value. |
| onSelect | (selected, day, modifiers, event) => void | — | Called with the new selection. |
| required | boolean | false | Clicking the selected day keeps it rather than clearing it. |
| disabled | Matcher | Matcher[] | — | Days that can't be picked: dates, { before }, { after }, { dayOfWeek } or a function. |
| weekStartsOn | 0 – 6 | 1 | Monday, as in every Okami market. 0 is Sunday. |
| locale | Locale | English | From react-day-picker/locale (sk, cs, enGB): month and weekday names. |
| defaultMonth | Date | today's month | The month shown first. |
| className | string | — | Layout only. The calendar has no surface of its own. |
Every other DayPicker prop passes through: month, numberOfMonths, startMonth, endMonth, showOutsideDays, …
Guidelines
- Disable what can't be booked: closed days, the past, days with no free slot. Don't let a client pick a day only to be told it is full.
- Black marks the selection and nothing else. Don't colour weekends, holidays or busy days. Disable them, or say it in words beside the calendar.
- Say the chosen day in words next to it (“Thursday 15 October”), in the page's language. A disc in a grid is easy to misread.
- Put a calendar in a popover or on a surface; it has none of its own. In a popover, use
flushand close it on pick.