Calendar
A date grid built on react-day-picker. Supports single, multiple and range selection, and composes into date pickers inside a Popover.
Playground
const [date, setDate] = React.useState<Date | undefined>(new Date())
<Calendar
mode="single"
selected={date}
onSelect={setDate}
/>Preview
The default composition: pass mode="single" with a controlled selected date and an onSelect handler. The root renders as a floating card on a bg-popover surface with an elevation shadow.
const [date, setDate] = React.useState<Date | undefined>(new Date())
<Calendar mode="single" selected={date} onSelect={setDate} />Installation
npx shadcn@latest add https://espresso-base-ui.vercel.app/r/calendar.jsonUsage
import { Calendar, CalendarDayButton } from "@/components/ui/calendar"const [date, setDate] = React.useState<Date | undefined>(new Date())
return <Calendar mode="single" selected={date} onSelect={setDate} />Range selection
mode="range" selects a start and end date. The span between them gets a continuous bg-muted track while the endpoints fill with the primary color; add numberOfMonths to show months side by side.
const [range, setRange] = React.useState<DateRange | undefined>({
from: new Date(2026, 7, 5),
to: new Date(2026, 7, 14),
})
<Calendar
mode="range"
selected={range}
onSelect={setRange}
numberOfMonths={2}
defaultMonth={range?.from}
classNames={{
months:
"relative flex flex-row items-stretch [&>div+div]:border-l [&>div+div]:border-border [&>div]:py-2 [&>div]:px-2",
nav: "absolute inset-x-2 top-2 flex w-auto items-center justify-between gap-1",
root: "w-fit p-0!",
}}
/>Date picker in a popover
The most common real-world composition wraps the calendar in a Popover behind an outline Button trigger. Strip the calendar's own surface with border-0 shadow-none – inside a popover it already sits on a popover background.
<Popover>
<PopoverTrigger
render={
<Button variant="outline" className="w-56 justify-start gap-2 font-normal">
<CalendarIcon />
{picked ? format(picked, "PPP") : "Pick a date"}
</Button>
}
/>
<PopoverContent className="w-auto p-0" align="start" sideOffset={4}>
<Calendar
mode="single"
selected={picked}
onSelect={setPicked}
className="border-0 shadow-none"
/>
</PopoverContent>
</Popover>Presets
Pair the calendar with quick-pick shortcuts in a bordered side column – ghost buttons that set the date and jump the visible month via a controlled month / onMonthChange.
const [date, setDate] = React.useState<Date | undefined>(new Date())
const [month, setMonth] = React.useState(new Date())
<PopoverContent className="w-auto p-0" align="start" sideOffset={4}>
<div className="flex">
<div className="flex flex-col items-start gap-px border-r border-border p-1">
<Button variant="ghost" size="sm" className="text-sm" onClick={() => pick(addDays(new Date(), 1))}>
Tomorrow
</Button>
<Button variant="ghost" size="sm" className="text-sm" onClick={() => pick(addDays(new Date(), 7))}>
Next week
</Button>
</div>
<Calendar
mode="single"
selected={date}
onSelect={setDate}
month={month}
onMonthChange={setMonth}
className="border-0 shadow-none"
/>
</div>
</PopoverContent>Disabled dates
The disabled prop takes any DayPicker matcher – an array of dates, a range, or a dayOfWeek rule. Disabled cells drop to 50% opacity and stop receiving pointer events.
<Calendar
mode="single"
selected={date}
onSelect={setDate}
disabled={{ dayOfWeek: [0, 6] }}
/>API reference
Calendar wraps DayPicker from react-day-picker and forwards every DayPicker prop – mode, selected, onSelect, month, onMonthChange, defaultMonth, locale, formatters, components, classNames and friends. The props below are the ones the wrapper defaults or adds.
Calendar
| Prop | Type | Default | Description |
|---|---|---|---|
| mode | "single" | "multiple" | "range" | – | Selection behavior (from react-day-picker). Pair with selected / onSelect for controlled state. |
| showOutsideDays | boolean | true | Render the leading and trailing days of adjacent months in muted text. |
| captionLayout | "label" | "dropdown" | "dropdown-months" | "dropdown-years" | "label" | Month caption style – a static label or native month/year dropdowns (short month names by default). |
| buttonVariant | Button variant | "ghost" | Button variant applied to the previous/next month navigation buttons. |
| numberOfMonths | number | 1 | Months rendered side by side. With more than one, the caption centers instead of left-aligning. |
| showWeekNumber | boolean | false | Adds an ISO week-number column; the grid widens by one cell. |
| disabled | Matcher | Matcher[] | – | DayPicker matcher(s) for un-selectable days: dates, ranges, or { dayOfWeek: [...] }. |
| locale | Locale | – | date-fns locale; also drives the built-in short-month and narrow-weekday formatters. |
| Component | Description |
|---|---|
| Calendar | Root wrapper (data-slot="calendar"). Popover-style card surface that turns transparent inside card or popover content. |
| CalendarDayButton | The day-cell Button used via the DayPicker components override. Exposes data-day, data-selected-single, data-range-start, data-range-middle and data-range-end for styling; auto-focuses when DayPicker marks it focused. |
Styling hooks & accessibility
Cell geometry is driven by two CSS variables set on the root: --cell-size (default 1.5rem) and --cell-radius. Override them from className – e.g. [--cell-size:2.75rem] for a larger grid. Day buttons reflect their selection state as data attributes (data-selected-single, data-range-start, data-range-middle, data-range-end), and the navigation chevrons flip automatically in RTL layouts. Keyboard navigation, focus management and aria roles come from react-day-picker; nav buttons expose aria-disabled when no previous/next month is available.
<Calendar
mode="single"
className="[--cell-size:2.75rem]"
/>