Components
Calendar
Preline-styled calendar powered by Vanilla Calendar Pro for single, range, time, and confirmable workflows.
Calendar renders a Preline-inspired shell and uses Vanilla Calendar Pro (VCP) as the grid/time engine. Month/year navigation and range selection are handled natively by VCP — no separate React header layer.
Installing
pnpm dlx shadcn@latest add https://ui.isaacfei.com/r/calendar.jsonnpx shadcn@latest add https://ui.isaacfei.com/r/calendar.jsonyarn dlx shadcn@latest add https://ui.isaacfei.com/r/calendar.jsonbun x shadcn@latest add https://ui.isaacfei.com/r/calendar.jsonOr with a namespace: npx shadcn@latest add @f-ui/calendar.
Usage
import { Calendar } from "@/components/f-ui/calendar/calendar";
<Calendar
mode="range"
value={range}
onChange={setRange}
visibleMonths={2}
aria-label="Travel dates"
/>;Examples
Single Date
Basic calendar with a Today footer action.
"use client";
import { useState } from "react";
import { Calendar } from "@/components/f-ui/calendar/calendar";
export function CalendarDemo() {
const [date, setDate] = useState<Date | undefined>(new Date());
return (
<div className="max-w-sm">
<Calendar
mode="single"
value={date ?? null}
onChange={setDate}
showToday
aria-label="Booking date"
/>
</div>
);
}Two-Month Range
Preline-style dual panel range calendar.
"use client";
import { useState } from "react";
import { Calendar } from "@/components/f-ui/calendar/calendar";
import type { DateRangeValue } from "@/components/f-ui/date-internals/date-value";
export function CalendarRangeDemo() {
const [range, setRange] = useState<DateRangeValue | undefined>();
return (
<div className="max-w-xl w-full">
<Calendar
mode="range"
value={range}
onChange={setRange}
visibleMonths={2}
showToday
aria-label="Trip dates"
/>
</div>
);
}Single Panel Range
Range mode with visibleMonths={1}.
"use client";
import { useMemo, useState } from "react";
import { Calendar } from "@/components/f-ui/calendar/calendar";
import type { DateRangeValue } from "@/components/f-ui/date-internals/date-value";
export function CalendarRangeSinglePaneDemo() {
const initial = useMemo(() => {
const now = new Date();
return {
from: new Date(now.getFullYear(), now.getMonth(), 5),
to: new Date(now.getFullYear(), now.getMonth(), 16),
} satisfies DateRangeValue;
}, []);
const [range, setRange] = useState<DateRangeValue | undefined>(initial);
return (
<div className="max-w-xl w-full">
<Calendar
mode="range"
value={range}
onChange={setRange}
visibleMonths={1}
showToday
aria-label="Booking window — single-pane range"
/>
</div>
);
}Multi-Month Single Mode
Three visible months for quick scanning.
"use client";
import { useState } from "react";
import { Calendar } from "@/components/f-ui/calendar/calendar";
export function CalendarMultiMonthDemo() {
const [date, setDate] = useState<Date | undefined>(new Date());
return (
<div className="max-w-full overflow-x-auto pb-1">
<Calendar
mode="single"
visibleMonths={2}
value={date ?? null}
onChange={setDate}
showToday
className="min-w-fit"
aria-label="Multi-month picker"
/>
</div>
);
}Header Controls
VCP native month/year pickers with minValue/maxValue.
"use client";
import { useState } from "react";
import { Calendar } from "@/components/f-ui/calendar/calendar";
const minDate = new Date(new Date().getFullYear() - 1, 0, 1);
const maxDate = new Date(new Date().getFullYear() + 1, 11, 31);
export function CalendarHeaderControlsDemo() {
const [date, setDate] = useState<Date | undefined>();
return (
<div className="max-w-sm">
<Calendar
mode="single"
value={date ?? null}
onChange={setDate}
minValue={minDate}
maxValue={maxDate}
aria-label="Fiscal date"
/>
</div>
);
}Headless Usage
useCalendar exposes the VCP grid mount ref and footer actions so you can rebuild shell/layout while keeping core selection logic.
"use client";
import { useState } from "react";
import type { DateRangeValue } from "@/components/f-ui/date-internals/date-value";
import { CalendarBody } from "@/components/f-ui/calendar/calendar-parts/calendar-body";
import { CalendarFooter } from "@/components/f-ui/calendar/calendar-parts/calendar-footer";
import { CalendarShell } from "@/components/f-ui/calendar/calendar-parts/calendar-shell";
import { useCalendarI18n } from "@/components/f-ui/calendar/hooks/use-calendar-i18n";
import { useCalendar } from "@/components/f-ui/calendar/use-calendar";
export function CalendarHeadlessDemo() {
const [range, setRange] = useState<DateRangeValue | undefined>();
const { t, locale } = useCalendarI18n();
const calendar = useCalendar({
mode: "range",
value: range,
onChange: setRange,
locale,
t,
visibleMonths: 2,
confirmable: true,
showToday: false,
});
return (
<CalendarShell>
<CalendarBody
gridRefs={calendar.gridRefs}
visibleMonths={calendar.visibleMonths}
/>
<CalendarFooter
mode={calendar.mode}
showToday={calendar.showToday}
confirmable={calendar.confirmable}
rangeSummary={calendar.rangeSummary}
disabled={calendar.disabled}
todayLabel={t("today")}
cancelLabel={t("cancel")}
applyLabel={t("apply")}
canApply={calendar.canApply}
onToday={calendar.onToday}
onCancel={calendar.onCancel}
onApply={calendar.onApply}
/>
</CalendarShell>
);
}Composition
Calendar
├── CalendarShell
├── CalendarBody
│ └── CalendarGridMount (VCP grid + native month/year headers)
└── CalendarFooter (Today / Cancel / Apply)API Reference
Props
| Prop | Type | Default |
|---|---|---|
mode | "single" | "range" | required |
value | Date | null or DateRangeValue | — |
onChange | (dateOrRange) => void | — |
locale | string (BCP 47) | i18n/provider locale |
minValue / maxValue | Date | — |
isDateUnavailable | (date: Date) => boolean | — |
firstDayOfWeek | 0 | 1 | 2 | 3 | 4 | 5 | 6 | locale default |
disabled | boolean | false |
visibleMonths | 1 | 2 | 1 |
withTime | boolean | false |
confirmable | boolean | false |
showToday | boolean | single && !confirmable |
className | string | — |
classNames | Partial<Record<CalendarSlot, string>> | — |
t | CalendarTranslateFn | built-in/provider |
Slots
| Slot | Element |
|---|---|
root | Outer shell |
body | Calendar body wrapper |
grid | VCP grid mount wrapper |
footer | Footer actions |
Hook - useCalendar(options)
| Return | Type |
|---|---|
gridRefs | RefObject<HTMLDivElement>[] |
showToday / confirmable / canApply | boolean |
rangeSummary | string | undefined |
onToday / onCancel / onApply | () => void |