Calendar
A date field component that allows users to enter and edit date. Built with Base UI and Tailwind CSS. Copy-paste ready.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import * as React from "react";
import { Calendar } from "@/components/ui/calendar";
export default function Particle() {
const [date, setDate] = React.useState<Date | undefined>(new Date());
return <Calendar mode="single" onSelect={setDate} selected={date} />;
}
Installation
pnpm dlx shadcn@latest add @cnippet/calendar
Examples
Large Cells
A single-date picker with enlarged day cells via --cell-size for a more spacious, touch-friendly layout.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import * as React from "react";
import { Calendar } from "@/components/ui/calendar";
export default function Particle() {
const [date, setDate] = React.useState<Date | undefined>(new Date());
return (
<Calendar
className="[--cell-size:--spacing(11)] sm:[--cell-size:--spacing(10)]"
mode="single"
onSelect={setDate}
selected={date}
/>
);
}
Date Range
A range picker using mode="range" that lets users select a start and end date with the days between highlighted.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import * as React from "react";
import type { DateRange } from "react-day-picker";
import { Calendar } from "@/components/ui/calendar";
export default function Particle() {
const [range, setRange] = React.useState<DateRange | undefined>({
from: new Date(),
to: new Date(new Date().setDate(new Date().getDate() + 7)),
});
return (
<Calendar
className="mx-auto"
mode="range"
onSelect={setRange}
selected={range}
/>
);
}
Dropdown Navigation
Year and month selection via native dropdowns.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import * as React from "react";
import { Calendar } from "@/components/ui/calendar";
export default function Particle() {
const [date, setDate] = React.useState<Date | undefined>(new Date());
return (
<Calendar
captionLayout="dropdown"
endMonth={new Date(2030, 11)}
mode="single"
onSelect={setDate}
selected={date}
startMonth={new Date(1930, 0)}
/>
);
}
Custom Select Dropdown
Replaces the native dropdown with a styled Select component.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import * as React from "react";
import type { DropdownProps } from "react-day-picker";
import { Calendar } from "@/components/ui/calendar";
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
function CalendarDropdown(props: DropdownProps) {
const { options, value, onChange, "aria-label": ariaLabel } = props;
const handleValueChange = (newValue: string | null) => {
if (onChange && newValue) {
const syntheticEvent = {
target: { value: newValue },
} as React.ChangeEvent<HTMLSelectElement>;
onChange(syntheticEvent);
}
};
const items =
options?.map((option) => ({
disabled: option.disabled,
label: option.label,
value: option.value.toString(),
})) ?? [];
return (
<Select
aria-label={ariaLabel}
items={items}
onValueChange={handleValueChange}
value={value?.toString()}
>
<SelectTrigger className="min-w-none">
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map((item) => (
<SelectItem
disabled={item.disabled}
key={item.value}
value={item.value}
>
{item.label}
</SelectItem>
))}
</SelectPopup>
</Select>
);
}
export default function Particle() {
const [date, setDate] = React.useState<Date | undefined>(new Date());
return (
<Calendar
captionLayout="dropdown"
components={{ Dropdown: CalendarDropdown }}
endMonth={new Date(2030, 11)}
mode="single"
onSelect={setDate}
selected={date}
startMonth={new Date(1930, 0)}
/>
);
}
Custom Combobox Dropdown
Replaces the native dropdown with a searchable Combobox component.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import * as React from "react";
import type { DropdownProps } from "react-day-picker";
import { Calendar } from "@/components/ui/calendar";
import {
Combobox,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
ComboboxPopup,
} from "@/components/ui/combobox";
interface DropdownItem {
disabled?: boolean;
label: string;
value: string;
}
function CalendarDropdown(props: DropdownProps) {
const { options, value, onChange, "aria-label": ariaLabel } = props;
const items: DropdownItem[] =
options?.map((option) => ({
disabled: option.disabled,
label: option.label,
value: option.value.toString(),
})) ?? [];
const selectedItem = items.find((item) => item.value === value?.toString());
const handleValueChange = (newValue: DropdownItem | null) => {
if (onChange && newValue) {
const syntheticEvent = {
target: { value: newValue.value },
} as React.ChangeEvent<HTMLSelectElement>;
onChange(syntheticEvent);
}
};
return (
<Combobox
aria-label={ariaLabel}
autoHighlight
items={items}
onValueChange={handleValueChange}
value={selectedItem}
>
<ComboboxInput
className="**:[input]:w-0 **:[input]:flex-1"
onFocus={(e) => e.currentTarget.select()}
/>
<ComboboxPopup aria-label={ariaLabel}>
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item: DropdownItem) => (
<ComboboxItem
disabled={item.disabled}
key={item.value}
value={item}
>
{item.label}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxPopup>
</Combobox>
);
}
export default function Particle() {
const [date, setDate] = React.useState<Date | undefined>(new Date());
return (
<Calendar
captionLayout="dropdown"
components={{ Dropdown: CalendarDropdown }}
endMonth={new Date(2030, 11)}
mode="single"
onSelect={setDate}
selected={date}
startMonth={new Date(1930, 0)}
/>
);
}
Multiple Selection
Pick any number of individual dates — selected dates are listed below.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Click any day to select multiple dates.
"use client";
import * as React from "react";
import { Calendar } from "@/components/ui/calendar";
export default function Particle() {
const [dates, setDates] = React.useState<Date[] | undefined>([]);
const sorted = [...(dates ?? [])].sort((a, b) => a.getTime() - b.getTime());
return (
<div className="flex flex-col gap-4">
<Calendar mode="multiple" onSelect={setDates} selected={dates} />
<div className="min-h-12 rounded-lg border border-dashed px-4 py-3">
{sorted.length > 0 ? (
<div className="flex flex-col gap-2">
<p className="font-medium text-muted-foreground text-xs">
{sorted.length} date{sorted.length !== 1 ? "s" : ""} selected
</p>
<ul className="flex flex-wrap gap-1.5">
{sorted.map((d) => (
<li
className="rounded-md bg-secondary px-2 py-0.5 font-medium text-xs"
key={d.toISOString()}
>
{d.toLocaleDateString("en-US", {
day: "numeric",
month: "short",
})}
</li>
))}
</ul>
</div>
) : (
<p className="text-muted-foreground text-sm">
Click any day to select multiple dates.
</p>
)}
</div>
</div>
);
}
Appointment Booking
Weekends and fully-booked dates are disabled; only future weekdays are selectable.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Weekdays only · Greyed-out dates are fully booked.
"use client";
import * as React from "react";
import { Calendar } from "@/components/ui/calendar";
const today = new Date();
today.setHours(0, 0, 0, 0);
// Simulate fully-booked dates
const y = today.getFullYear();
const m = today.getMonth();
const bookedDates = [
new Date(y, m, 8),
new Date(y, m, 9),
new Date(y, m, 15),
new Date(y, m, 22),
new Date(y, m + 1, 5),
new Date(y, m + 1, 12),
];
export default function Particle() {
const [date, setDate] = React.useState<Date | undefined>();
return (
<div className="flex flex-col gap-3">
<Calendar
disabled={[{ before: today }, { dayOfWeek: [0, 6] }, ...bookedDates]}
mode="single"
onSelect={setDate}
selected={date}
/>
<div className="rounded-lg border px-4 py-3 text-sm">
{date ? (
<p>
Appointment:{" "}
<span className="font-semibold">
{date.toLocaleDateString("en-US", {
day: "numeric",
month: "long",
weekday: "long",
})}
</span>
</p>
) : (
<p className="text-muted-foreground">
Weekdays only · Greyed-out dates are fully booked.
</p>
)}
</div>
</div>
);
}
Two-Month Range Picker
Side-by-side months for easier date-range selection, with a nights counter.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Select a check-in date.
"use client";
import * as React from "react";
import type { DateRange } from "react-day-picker";
import { Calendar } from "@/components/ui/calendar";
function formatShort(d: Date) {
return d.toLocaleDateString("en-US", {
day: "numeric",
month: "short",
year: "numeric",
});
}
function nightsBetween(a: Date, b: Date) {
return Math.round(Math.abs(b.getTime() - a.getTime()) / 86_400_000);
}
export default function Particle() {
const [range, setRange] = React.useState<DateRange | undefined>();
const nights =
range?.from && range?.to ? nightsBetween(range.from, range.to) : 0;
return (
<div className="flex flex-col gap-3">
<Calendar
mode="range"
numberOfMonths={2}
onSelect={setRange}
selected={range}
/>
<div className="rounded-lg border px-4 py-3 text-sm">
{range?.from ? (
<p className="flex flex-wrap items-center gap-1.5">
<span>{formatShort(range.from)}</span>
<span className="text-muted-foreground">→</span>
{range.to ? (
<>
<span>{formatShort(range.to)}</span>
{nights > 0 && (
<span className="rounded-full bg-secondary px-2 py-0.5 font-medium text-xs">
{nights} night{nights !== 1 ? "s" : ""}
</span>
)}
</>
) : (
<span className="text-muted-foreground">pick end date</span>
)}
</p>
) : (
<p className="text-muted-foreground">Select a check-in date.</p>
)}
</div>
</div>
);
}
Event Indicators
Custom DayButton renders colored dots for meetings, deadlines, and holidays.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import * as React from "react";
import type { CalendarDay, Modifiers } from "react-day-picker";
import { Calendar } from "@/components/ui/calendar";
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
type EventType = "meeting" | "deadline" | "holiday";
const events: Record<string, EventType> = {
[new Date(y, m, 3).toDateString()]: "meeting",
[new Date(y, m, 7).toDateString()]: "deadline",
[new Date(y, m, 10).toDateString()]: "meeting",
[new Date(y, m, 14).toDateString()]: "holiday",
[new Date(y, m, 17).toDateString()]: "meeting",
[new Date(y, m, 21).toDateString()]: "deadline",
[new Date(y, m, 24).toDateString()]: "meeting",
[new Date(y, m, 28).toDateString()]: "meeting",
};
const dotColor: Record<EventType, string> = {
deadline: "bg-red-500",
holiday: "bg-amber-500",
meeting: "bg-blue-500",
};
interface DayButtonProps extends React.ComponentProps<"button"> {
day: CalendarDay;
modifiers: Modifiers;
}
function EventDayButton({
day,
modifiers,
children,
...buttonProps
}: DayButtonProps) {
const eventType = events[day.date.toDateString()];
return (
<button {...buttonProps}>
{children}
{eventType && (
<span
className={`absolute bottom-1 left-1/2 size-1 -translate-x-1/2 rounded-full ${dotColor[eventType]}`}
/>
)}
</button>
);
}
export default function Particle() {
const [date, setDate] = React.useState<Date | undefined>();
return (
<div className="flex flex-col gap-4">
<Calendar
components={{ DayButton: EventDayButton }}
mode="single"
onSelect={setDate}
selected={date}
/>
<div className="flex items-center justify-center gap-5 text-muted-foreground text-xs">
<span className="flex items-center gap-1.5">
<span className="size-2 rounded-full bg-blue-500" />
Meeting
</span>
<span className="flex items-center gap-1.5">
<span className="size-2 rounded-full bg-red-500" />
Deadline
</span>
<span className="flex items-center gap-1.5">
<span className="size-2 rounded-full bg-amber-500" />
Holiday
</span>
</div>
</div>
);
}
With Presets
Quick-select buttons for common offsets (Today, Tomorrow, Next Monday, etc.) above a full calendar.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
function addDays(base: Date, days: number): Date {
const d = new Date(base);
d.setDate(d.getDate() + days);
return d;
}
function nextWeekday(dayOfWeek: number): Date {
const today = new Date();
const diff = (dayOfWeek - today.getDay() + 7) % 7 || 7;
return addDays(today, diff);
}
const today = new Date();
const presets = [
{ date: today, label: "Today" },
{ date: addDays(today, 1), label: "Tomorrow" },
{ date: nextWeekday(1), label: "Next Monday" },
{ date: addDays(today, 7), label: "In 1 week" },
{ date: addDays(today, 14), label: "In 2 weeks" },
{ date: addDays(today, 30), label: "In 1 month" },
];
export default function Particle() {
const [date, setDate] = React.useState<Date | undefined>(today);
return (
<div className="overflow-hidden rounded-xl border">
<div className="flex flex-wrap gap-1.5 border-b bg-muted/40 px-3 py-2.5">
{presets.map((p) => (
<Button
className="h-7 text-xs"
key={p.label}
onClick={() => setDate(p.date)}
size="sm"
variant={
date?.toDateString() === p.date.toDateString()
? "default"
: "outline"
}
>
{p.label}
</Button>
))}
</div>
<Calendar mode="single" onSelect={setDate} selected={date} />
{date && (
<div className="border-t px-4 py-2.5 text-center text-muted-foreground text-sm">
{date.toLocaleDateString("en-US", {
day: "numeric",
month: "long",
weekday: "long",
year: "numeric",
})}
</div>
)}
</div>
);
}
Date of Birth
Dropdown navigation spanning 1900 to today with future dates disabled and live age calculation.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Select your date of birth.
"use client";
import * as React from "react";
import type { DropdownProps } from "react-day-picker";
import { Calendar } from "@/components/ui/calendar";
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
function CalendarDropdown(props: DropdownProps) {
const { options, value, onChange, "aria-label": ariaLabel } = props;
const handleValueChange = (newValue: string | null) => {
if (onChange && newValue) {
const syntheticEvent = {
target: { value: newValue },
} as React.ChangeEvent<HTMLSelectElement>;
onChange(syntheticEvent);
}
};
const items =
options?.map((option) => ({
disabled: option.disabled,
label: option.label,
value: option.value.toString(),
})) ?? [];
return (
<Select
aria-label={ariaLabel}
items={items}
onValueChange={handleValueChange}
value={value?.toString()}
>
<SelectTrigger className="min-w-none">
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map((item) => (
<SelectItem
disabled={item.disabled}
key={item.value}
value={item.value}
>
{item.label}
</SelectItem>
))}
</SelectPopup>
</Select>
);
}
const today = new Date();
today.setHours(0, 0, 0, 0);
function calcAge(dob: Date): number {
const diff = today.getTime() - dob.getTime();
return Math.floor(diff / (365.25 * 24 * 60 * 60 * 1000));
}
export default function Particle() {
const [date, setDate] = React.useState<Date | undefined>();
const age = date ? calcAge(date) : null;
return (
<div className="flex flex-col gap-3">
<Calendar
captionLayout="dropdown"
components={{ Dropdown: CalendarDropdown }}
defaultMonth={new Date(1990, 0)}
disabled={{ after: today }}
endMonth={today}
mode="single"
onSelect={setDate}
selected={date}
startMonth={new Date(1900, 0)}
/>
<div className="rounded-lg border px-4 py-3 text-sm">
{date && age !== null ? (
<p>
Date of birth:{" "}
<span className="font-semibold">
{date.toLocaleDateString("en-US", {
day: "numeric",
month: "long",
year: "numeric",
})}
</span>
<span className="ml-2 text-muted-foreground">
({age} years old)
</span>
</p>
) : (
<p className="text-muted-foreground">Select your date of birth.</p>
)}
</div>
</div>
);
}
Week Picker
Clicking any day snaps the selection to its full ISO Monday–Sunday week, with week numbers shown.
| Mo | Tu | We | Th | Fr | Sa | Su | |
|---|---|---|---|---|---|---|---|
| 23 | |||||||
| 24 | |||||||
| 25 | |||||||
| 26 | |||||||
| 27 |
Week selected: Jun 1 – 2026 (day: 7)
"use client";
import * as React from "react";
import type { DateRange } from "react-day-picker";
import { Calendar } from "@/components/ui/calendar";
// Snaps a date to the Monday–Sunday ISO week containing it.
function isoWeekRange(date: Date): DateRange {
const d = new Date(date);
d.setHours(0, 0, 0, 0);
const day = d.getDay();
const diffToMonday = day === 0 ? -6 : 1 - day;
const from = new Date(d);
from.setDate(d.getDate() + diffToMonday);
const to = new Date(from);
to.setDate(from.getDate() + 6);
return { from, to };
}
function formatWeekLabel(range: DateRange): string {
const { from, to } = range;
if (!from || !to) return "";
const sameYear = from.getFullYear() === to.getFullYear();
const sameMonth = sameYear && from.getMonth() === to.getMonth();
const fromStr = from.toLocaleDateString("en-US", {
day: "numeric",
month: "short",
});
const toStr = to.toLocaleDateString("en-US", {
day: "numeric",
month: sameMonth ? undefined : "short",
year: "numeric",
});
return `${fromStr} – ${toStr}`;
}
export default function Particle() {
const [week, setWeek] = React.useState<DateRange>(isoWeekRange(new Date()));
function handleSelect(range: DateRange | undefined) {
// react-day-picker provides `from` as the newly clicked day;
// snap the whole selection to that day's ISO week.
if (range?.from) setWeek(isoWeekRange(range.from));
}
return (
<div className="flex flex-col gap-3">
<Calendar
ISOWeek
mode="range"
onSelect={handleSelect}
selected={week}
showWeekNumber
/>
<div className="rounded-lg border px-4 py-3 text-sm">
{week.from ? (
<p>
Week selected:{" "}
<span className="font-semibold">{formatWeekLabel(week)}</span>
</p>
) : (
<p className="text-muted-foreground">
Click any day to select its full week.
</p>
)}
</div>
</div>
);
}
Habit Tracker
A custom DayButton overlays a check icon on completed days with emerald styling, and a footer row shows the monthly streak count.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import { CheckIcon } from "lucide-react";
import type * as React from "react";
import type { CalendarDay, Modifiers } from "react-day-picker";
import { Calendar } from "@/components/ui/calendar";
const today = new Date();
const y = today.getFullYear();
const m = today.getMonth();
const completedDays = new Set(
[1, 2, 3, 5, 6, 8, 9, 10, 12, 13, 15, 16, 17, 19, 20].map((d) =>
new Date(y, m, d).toDateString(),
),
);
interface DayButtonProps extends React.ComponentProps<"button"> {
day: CalendarDay;
modifiers: Modifiers;
}
function HabitDayButton({
day,
modifiers,
children,
...props
}: DayButtonProps) {
const done = completedDays.has(day.date.toDateString());
const isFuture = day.date > today;
return (
<button
{...props}
className={[
props.className,
done && !modifiers.selected
? "relative bg-emerald-50 text-emerald-700 dark:bg-emerald-950/50 dark:text-emerald-400"
: "",
]
.filter(Boolean)
.join(" ")}
>
{children}
{done && !isFuture && (
<CheckIcon className="absolute right-0.5 bottom-0.5 size-2.5 text-emerald-500" />
)}
</button>
);
}
export default function Particle() {
const streak = [...completedDays].filter((d) => new Date(d) <= today).length;
return (
<div className="flex flex-col gap-3">
<Calendar
components={{ DayButton: HabitDayButton }}
disabled={{ after: today }}
mode="single"
onSelect={() => {}}
selected={undefined}
/>
<div className="flex items-center justify-between rounded-lg border px-4 py-2.5 text-sm">
<span className="text-muted-foreground">Days completed this month</span>
<span className="font-semibold">
{streak} / {today.getDate()}
</span>
</div>
</div>
);
}
Sprint Planner
A two-month range picker for sprint planning that displays start date, end date, and total duration alongside an auto-calculated sprint number badge.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import * as React from "react";
import type { DateRange } from "react-day-picker";
import { Badge } from "@/components/ui/badge";
import { Calendar } from "@/components/ui/calendar";
const today = new Date();
function diffDays(a: Date, b: Date) {
return Math.round((b.getTime() - a.getTime()) / 86400000);
}
export default function Particle() {
const [range, setRange] = React.useState<DateRange | undefined>();
const duration =
range?.from && range?.to ? diffDays(range.from, range.to) + 1 : null;
const sprintNum = range?.from
? Math.ceil(
(diffDays(new Date(today.getFullYear(), 0, 1), range.from) + 1) / 14,
)
: null;
return (
<div className="flex flex-col gap-3">
<Calendar
disabled={{ before: today }}
mode="range"
numberOfMonths={2}
onSelect={setRange}
selected={range}
/>
<div className="grid grid-cols-3 gap-2 rounded-lg border px-4 py-3 text-sm">
<div className="flex flex-col gap-0.5">
<span className="text-muted-foreground text-xs">Start</span>
<span className="font-medium">
{range?.from
? range.from.toLocaleDateString("en-US", {
day: "numeric",
month: "short",
})
: "—"}
</span>
</div>
<div className="flex flex-col gap-0.5">
<span className="text-muted-foreground text-xs">End</span>
<span className="font-medium">
{range?.to
? range.to.toLocaleDateString("en-US", {
day: "numeric",
month: "short",
})
: "—"}
</span>
</div>
<div className="flex flex-col gap-0.5">
<span className="text-muted-foreground text-xs">Duration</span>
<span className="font-medium">
{duration ? (
<span className="flex items-center gap-1.5">
{duration}d
{sprintNum && (
<Badge size="sm" variant="secondary">
S{sprintNum}
</Badge>
)}
</span>
) : (
"—"
)}
</span>
</div>
</div>
</div>
);
}
Time Off Request
A range picker paired with leave type pill selectors (Vacation, Sick Leave, Personal, WFH) and a submit button that clears the form on success.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import * as React from "react";
import type { DateRange } from "react-day-picker";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
const today = new Date();
const TIME_OFF_TYPES = ["Vacation", "Sick Leave", "Personal", "Work From Home"];
export default function Particle() {
const [range, setRange] = React.useState<DateRange | undefined>();
const [type, setType] = React.useState("Vacation");
const [submitted, setSubmitted] = React.useState(false);
const days =
range?.from && range?.to
? Math.round((range.to.getTime() - range.from.getTime()) / 86400000) + 1
: null;
function handleSubmit() {
if (!range?.from || !range?.to) return;
setSubmitted(true);
setTimeout(() => {
setSubmitted(false);
setRange(undefined);
}, 2500);
}
return (
<div className="flex flex-col gap-3">
<div className="flex flex-wrap gap-1.5">
{TIME_OFF_TYPES.map((t) => (
<button
className={`rounded-full border px-3 py-0.5 font-medium text-xs transition-colors ${
type === t
? "border-primary bg-primary text-primary-foreground"
: "text-muted-foreground hover:border-foreground/30"
}`}
key={t}
onClick={() => setType(t)}
type="button"
>
{t}
</button>
))}
</div>
<Calendar
disabled={{ before: today }}
mode="range"
onSelect={setRange}
selected={range}
/>
<div className="flex items-center justify-between">
<span className="text-muted-foreground text-sm">
{days
? `${days} day${days !== 1 ? "s" : ""} · ${type}`
: "Select dates"}
</span>
<Button
disabled={!range?.from || !range?.to || submitted}
onClick={handleSubmit}
size="sm"
>
{submitted ? "Submitted!" : "Request Time Off"}
</Button>
</div>
</div>
);
}
Mini Year View
All twelve months of the current year rendered as compact calendar grids side by side, allowing a full year at a glance with single-date selection.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import * as React from "react";
import { Calendar } from "@/components/ui/calendar";
const today = new Date();
export default function Particle() {
const [selected, setSelected] = React.useState<Date | undefined>();
const months = Array.from({ length: 12 }, (_, i) => i);
return (
<div className="flex flex-col gap-3">
<div className="grid grid-cols-3 gap-2 sm:grid-cols-4">
{months.map((month) => (
<div className="overflow-hidden rounded-lg border" key={month}>
<Calendar
className="[&_.rdp-nav]:hidden [&_table]:text-[10px] [&_td]:p-0 [&_th]:p-0 [&_th]:text-[9px]"
defaultMonth={new Date(today.getFullYear(), month)}
mode="single"
onSelect={setSelected}
selected={selected}
/>
</div>
))}
</div>
{selected && (
<p className="text-center text-muted-foreground text-sm">
Selected:{" "}
<span className="font-medium text-foreground">
{selected.toLocaleDateString("en-US", {
day: "numeric",
month: "long",
year: "numeric",
})}
</span>
</p>
)}
</div>
);
}
Time Slot Booking
Selecting a date reveals a grid of available and booked time slots; choosing a free slot enables a confirm button that resets the form on success.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
"use client";
import * as React from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
const today = new Date();
const SLOTS = [
"9:00 AM",
"10:00 AM",
"11:00 AM",
"1:00 PM",
"2:00 PM",
"3:00 PM",
"4:00 PM",
];
const BOOKED: Record<string, string[]> = {
[new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + 1,
).toDateString()]: ["10:00 AM", "2:00 PM"],
[new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + 2,
).toDateString()]: ["9:00 AM", "1:00 PM", "3:00 PM"],
};
export default function Particle() {
const [date, setDate] = React.useState<Date | undefined>();
const [slot, setSlot] = React.useState<string | undefined>();
const [confirmed, setConfirmed] = React.useState(false);
const booked = date ? (BOOKED[date.toDateString()] ?? []) : [];
function handleConfirm() {
setConfirmed(true);
setTimeout(() => {
setConfirmed(false);
setDate(undefined);
setSlot(undefined);
}, 2500);
}
return (
<div className="flex flex-col gap-3">
<Calendar
disabled={{ before: today }}
mode="single"
onSelect={(d) => {
setDate(d);
setSlot(undefined);
}}
selected={date}
/>
{date && (
<div className="flex flex-col gap-2">
<p className="text-muted-foreground text-xs">
Available slots for{" "}
<span className="font-medium text-foreground">
{date.toLocaleDateString("en-US", {
day: "numeric",
month: "short",
})}
</span>
</p>
<div className="grid grid-cols-4 gap-1.5">
{SLOTS.map((s) => {
const isBooked = booked.includes(s);
return (
<button
className={`rounded-md border px-2 py-1.5 font-medium text-xs transition-colors ${
isBooked
? "cursor-not-allowed border-dashed text-muted-foreground/40"
: slot === s
? "border-primary bg-primary text-primary-foreground"
: "text-foreground hover:border-foreground/30"
}`}
disabled={isBooked}
key={s}
onClick={() => setSlot(s)}
type="button"
>
{s}
</button>
);
})}
</div>
<div className="flex items-center justify-between pt-1">
<div className="flex items-center gap-1.5">
{slot && (
<Badge variant="secondary">
{date.toLocaleDateString("en-US", {
day: "numeric",
month: "short",
})}{" "}
· {slot}
</Badge>
)}
</div>
<Button
disabled={!slot || confirmed}
onClick={handleConfirm}
size="sm"
>
{confirmed ? "Booked!" : "Book Slot"}
</Button>
</div>
</div>
)}
</div>
);
}
On This Page

