Date Picker
A date picker component built with Calendar and Popover. Built with Base UI and Tailwind CSS. Copy-paste ready.
"use client";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
export default function Particle() {
const [date, setDate] = useState<Date | undefined>();
return (
<Popover>
<PopoverTrigger
render={<Button className="w-full justify-start" variant="outline" />}
>
<CalendarIcon aria-hidden="true" />
{date ? format(date, "PPP") : "Pick a date"}
</PopoverTrigger>
<PopoverPopup>
<Calendar
defaultMonth={date}
mode="single"
onSelect={setDate}
selected={date}
/>
</PopoverPopup>
</Popover>
);
}
Installation
pnpm dlx shadcn@latest add @cnippet/date-picker
Usage
A date picker is built by composing Calendar inside a Popover. There is no separate date-picker component file — copy the pieces you need.
import { useState } from "react"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover"export function DatePicker() {
const [date, setDate] = useState<Date>()
return (
<Popover>
<PopoverTrigger render={<Button variant="outline" />}>
<CalendarIcon />
{date ? format(date, "PPP") : "Pick a date"}
</PopoverTrigger>
<PopoverPopup>
<Calendar mode="single" selected={date} onSelect={setDate} />
</PopoverPopup>
</Popover>
)
}Examples
Date Range
A range picker using mode="range" that highlights all days between the selected start and end date.
"use client";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";
import type { DateRange } from "react-day-picker";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
export default function Particle() {
const [date, setDate] = useState<DateRange | undefined>();
return (
<Popover>
<PopoverTrigger
render={<Button className="w-full justify-start" variant="outline" />}
>
<CalendarIcon aria-hidden="true" />
{date?.from ? (
date.to ? (
<>
{format(date.from, "LLL dd, y")} - {format(date.to, "LLL dd, y")}
</>
) : (
format(date.from, "LLL dd, y")
)
) : (
<span>Pick a date range</span>
)}
</PopoverTrigger>
<PopoverPopup>
<Calendar
defaultMonth={date?.from}
mode="range"
onSelect={setDate}
selected={date}
/>
</PopoverPopup>
</Popover>
);
}
Dropdown Navigation
Year and month selection via a searchable Combobox dropdown, spanning 1900 to today — useful for date-of-birth or historical date inputs.
"use client";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import * as React from "react";
import type { DropdownProps } from "react-day-picker";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Combobox,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
ComboboxPopup,
} from "@/components/ui/combobox";
import { Field, FieldLabel } from "@/components/ui/field";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
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>();
const id = React.useId();
return (
<Field>
<FieldLabel htmlFor={id}>Start date</FieldLabel>
<Popover>
<PopoverTrigger
id={id}
render={<Button className="w-full justify-start" variant="outline" />}
>
<CalendarIcon aria-hidden="true" />
{date ? format(date, "PPP") : "Pick a date"}
</PopoverTrigger>
<PopoverPopup>
<Calendar
captionLayout="dropdown"
components={{ Dropdown: CalendarDropdown }}
defaultMonth={date}
endMonth={new Date()}
mode="single"
onSelect={setDate}
selected={date}
startMonth={new Date(1900, 0)}
/>
</PopoverPopup>
</Popover>
</Field>
);
}
With Presets
Quick-select buttons for common offsets (Today, Tomorrow, In 3 days, In a week) alongside a full calendar so users can jump to frequent dates in one click.
"use client";
import { addDays, format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
export default function Particle() {
const today = new Date();
const [month, setMonth] = useState(today);
const [date, setDate] = useState<Date | undefined>(today);
return (
<Popover>
<PopoverTrigger
render={<Button className="w-full justify-start" variant="outline" />}
>
<CalendarIcon aria-hidden="true" />
{date ? format(date, "PPP") : "Pick a date"}
</PopoverTrigger>
<PopoverPopup>
<div className="flex max-sm:flex-col">
<div className="relative py-1 ps-1 max-sm:order-1 max-sm:border-t">
<div className="flex h-full flex-col sm:border-e sm:pe-3">
<Button
className="w-full justify-start"
onClick={() => {
setDate(today);
setMonth(today);
}}
size="sm"
variant="ghost"
>
Today
</Button>
<Button
className="w-full justify-start"
onClick={() => {
const tomorrow = addDays(today, 1);
setDate(tomorrow);
setMonth(tomorrow);
}}
size="sm"
variant="ghost"
>
Tomorrow
</Button>
<Button
className="w-full justify-start"
onClick={() => {
const in3Days = addDays(today, 3);
setDate(in3Days);
setMonth(in3Days);
}}
size="sm"
variant="ghost"
>
In 3 days
</Button>
<Button
className="w-full justify-start"
onClick={() => {
const inAWeek = addDays(today, 7);
setDate(inAWeek);
setMonth(inAWeek);
}}
size="sm"
variant="ghost"
>
In a week
</Button>
</div>
</div>
<Calendar
className="max-sm:pb-3 sm:ps-2"
mode="single"
month={month}
onMonthChange={setMonth}
onSelect={setDate}
selected={date}
/>
</div>
</PopoverPopup>
</Popover>
);
}
With Input
Combines a native type="date" text field with a popover calendar — users can type a date directly or open the picker, and both stay in sync.
"use client";
import { format, isValid, parse } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
export default function Particle() {
const [date, setDate] = useState<Date | undefined>();
const [inputValue, setInputValue] = useState("");
const [month, setMonth] = useState<Date>(() => new Date());
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setInputValue(value);
if (value) {
const parsedDate = parse(value, "yyyy-MM-dd", new Date());
if (isValid(parsedDate)) {
setDate(parsedDate);
setMonth(parsedDate);
}
} else {
setDate(undefined);
}
};
const handleSelect = (selectedDate: Date | undefined) => {
setDate(selectedDate);
if (selectedDate) {
setInputValue(format(selectedDate, "yyyy-MM-dd"));
setMonth(selectedDate);
} else {
setInputValue("");
}
};
return (
<Popover>
<InputGroup>
<InputGroupInput
aria-label="Select date"
className="*:[input]:[&::-webkit-calendar-picker-indicator]:hidden *:[input]:[&::-webkit-calendar-picker-indicator]:appearance-none"
onChange={handleInputChange}
onClick={(e) => e.stopPropagation()}
type="date"
value={inputValue}
/>
<InputGroupAddon>
<PopoverTrigger
aria-label="Select date"
render={
<Button aria-label="Select date" size="icon-xs" variant="ghost" />
}
>
<CalendarIcon aria-hidden="true" />
</PopoverTrigger>
</InputGroupAddon>
</InputGroup>
<PopoverPopup align="start" alignOffset={-4} sideOffset={8}>
<Calendar
mode="single"
month={month}
onMonthChange={setMonth}
onSelect={handleSelect}
selected={date}
/>
</PopoverPopup>
</Popover>
);
}
Auto-close on Select
Closes the popover immediately after a date is chosen, reducing the number of clicks needed for a simple selection task.
"use client";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
export default function Particle() {
const [date, setDate] = useState<Date | undefined>();
const [open, setOpen] = useState(false);
const handleSelect = (selectedDate: Date | undefined) => {
setDate(selectedDate);
setOpen(false);
};
return (
<Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger
render={<Button className="w-full justify-start" variant="outline" />}
>
<CalendarIcon />
{date ? format(date, "PPP") : "Pick a date"}
</PopoverTrigger>
<PopoverPopup>
<Calendar mode="single" onSelect={handleSelect} selected={date} />
</PopoverPopup>
</Popover>
);
}
Appointment Booking
Displays a specialist card with available time slots that update per selected date — booked slots are struck through and disabled, confirming only when both a date and slot are chosen.
Dr. Sarah Chen
Cardiologist · NYC Health
"use client";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";
import { cn } from "@/lib/utils";
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
const TIME_SLOTS = [
"9:00 AM",
"9:30 AM",
"10:00 AM",
"10:30 AM",
"11:00 AM",
"2:00 PM",
"2:30 PM",
"3:00 PM",
"4:00 PM",
];
const BOOKED_BY_DOW: Record<number, string[]> = {
1: ["9:00 AM", "10:30 AM", "3:00 PM"],
2: ["9:30 AM", "2:00 PM"],
3: ["11:00 AM", "4:00 PM"],
4: ["9:00 AM", "2:30 PM", "3:00 PM"],
5: ["10:00 AM"],
};
export default function Particle() {
const [date, setDate] = useState<Date | undefined>();
const [selectedSlot, setSelectedSlot] = useState<string | undefined>();
const handleDateSelect = (d: Date | undefined) => {
setDate(d);
setSelectedSlot(undefined);
};
const booked = date ? (BOOKED_BY_DOW[date.getDay()] ?? []) : [];
return (
<div className="w-full max-w-sm space-y-4 rounded-xl border bg-background p-4 shadow-sm">
<div className="flex items-center gap-3 border-b pb-4">
<Avatar className="size-11">
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>SC</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<p className="font-semibold text-sm">Dr. Sarah Chen</p>
<p className="text-muted-foreground text-xs">
Cardiologist · NYC Health
</p>
</div>
<Badge variant="success">Available</Badge>
</div>
<Popover>
<PopoverTrigger
render={<Button className="w-full justify-start" variant="outline" />}
>
<CalendarIcon aria-hidden="true" />
{date ? format(date, "EEE, MMM dd, yyyy") : "Select a date"}
</PopoverTrigger>
<PopoverPopup>
<Calendar
disabled={[{ dayOfWeek: [0, 6] }, { before: new Date() }]}
mode="single"
onSelect={handleDateSelect}
selected={date}
/>
</PopoverPopup>
</Popover>
{date && (
<div>
<p className="mb-2 font-medium text-muted-foreground text-xs">
Available slots for {format(date, "MMM dd")}
</p>
<div className="flex flex-wrap gap-2">
{TIME_SLOTS.map((slot) => {
const isBooked = booked.includes(slot);
return (
<button
className={cn(
"rounded-md border px-3 py-1 font-medium text-xs transition-colors",
isBooked
? "cursor-not-allowed border-transparent bg-muted text-muted-foreground/40 line-through"
: selectedSlot === slot
? "border-primary bg-primary text-primary-foreground"
: "border-input bg-background hover:bg-accent",
)}
disabled={isBooked}
key={slot}
onClick={() => setSelectedSlot(slot)}
type="button"
>
{slot}
</button>
);
})}
</div>
</div>
)}
<Button className="w-full" disabled={!date || !selectedSlot}>
{selectedSlot && date
? `Book · ${format(date, "MMM dd")} at ${selectedSlot}`
: "Book Appointment"}
</Button>
</div>
);
}
Team Meeting Scheduler
Pick a date to instantly see each team member's availability (Free / Busy) based on their schedule — weekends are disabled, and the invite button stays locked until a valid day is chosen.
Schedule a Meeting
Pick a date to check team availability
"use client";
import { format } from "date-fns";
import { CalendarIcon, UsersIcon } from "lucide-react";
import { useState } from "react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
const TEAM = [
{ busyOn: [1, 3], id: 1, name: "Alice Park", role: "Design" },
{ busyOn: [2, 4], id: 2, name: "Ben Torres", role: "Engineering" },
{ busyOn: [1, 5], id: 3, name: "Celia Kim", role: "Product" },
{ busyOn: [3], id: 4, name: "Dan Moss", role: "Engineering" },
];
function initials(name: string) {
return name
.split(" ")
.map((n) => n[0])
.join("");
}
export default function Particle() {
const [date, setDate] = useState<Date | undefined>();
const freeCount = date
? TEAM.filter((m) => !m.busyOn.includes(date.getDay())).length
: 0;
return (
<div className="w-full max-w-sm space-y-4 rounded-xl border bg-background p-4 shadow-sm">
<div className="flex items-center gap-2">
<UsersIcon
aria-hidden="true"
className="size-4 text-muted-foreground"
/>
<div>
<h3 className="font-semibold text-sm">Schedule a Meeting</h3>
<p className="text-muted-foreground text-xs">
Pick a date to check team availability
</p>
</div>
</div>
<Popover>
<PopoverTrigger
render={<Button className="w-full justify-start" variant="outline" />}
>
<CalendarIcon aria-hidden="true" />
{date ? format(date, "EEE, MMM dd, yyyy") : "Pick a date"}
</PopoverTrigger>
<PopoverPopup>
<Calendar
disabled={[{ dayOfWeek: [0, 6] }, { before: new Date() }]}
mode="single"
onSelect={setDate}
selected={date}
/>
</PopoverPopup>
</Popover>
{date ? (
<div className="space-y-2">
<p className="text-muted-foreground text-xs">
{freeCount} of {TEAM.length} available on {format(date, "MMM dd")}
</p>
{TEAM.map((member) => {
const isBusy = member.busyOn.includes(date.getDay());
return (
<div className="flex items-center gap-3" key={member.id}>
<Avatar className="size-8">
<AvatarFallback className="text-xs">
{initials(member.name)}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<p className="truncate font-medium text-sm">{member.name}</p>
<p className="text-muted-foreground text-xs">{member.role}</p>
</div>
<Badge variant={isBusy ? "warning" : "success"}>
{isBusy ? "Busy" : "Free"}
</Badge>
</div>
);
})}
</div>
) : (
<div className="flex items-center justify-center gap-2 py-4">
{TEAM.map((member) => (
<Avatar className="size-8 ring-2 ring-background" key={member.id}>
<AvatarFallback className="text-xs">
{initials(member.name)}
</AvatarFallback>
</Avatar>
))}
</div>
)}
<Button className="w-full" disabled={!date || freeCount === 0}>
{date && freeCount > 0
? `Send invite · ${format(date, "MMM dd")}`
: date && freeCount === 0
? "No one available"
: "Send Invite"}
</Button>
</div>
);
}
Delivery Date Selector
Choose between Standard and Express shipping — the calendar disables ineligible dates per method (no weekends for Standard), and the estimated delivery window updates live as the user picks a date.
Choose Delivery
Estimated delivery: Mon, Jun 08 – Tue, Jun 09
//biome-ignore-all lint/style/noNonNullAssertion:<>
"use client";
import { addDays, format, isWeekend, startOfDay } from "date-fns";
import { PackageIcon, TruckIcon, ZapIcon } from "lucide-react";
import { useState } from "react";
import { cn } from "@/lib/utils";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
type ShippingMethod = "standard" | "express";
const METHODS = [
{
description: "3–5 business days",
icon: TruckIcon,
id: "standard" as ShippingMethod,
label: "Standard",
minDays: 3,
price: "Free",
},
{
description: "1–2 business days",
icon: ZapIcon,
id: "express" as ShippingMethod,
label: "Express",
minDays: 1,
price: "$9.99",
},
];
function nextBusinessDay(from: Date, minDays: number): Date {
let d = addDays(startOfDay(from), minDays);
while (isWeekend(d)) d = addDays(d, 1);
return d;
}
export default function Particle() {
const today = new Date();
const [method, setMethod] = useState<ShippingMethod>("standard");
const [date, setDate] = useState<Date | undefined>();
const selected = METHODS.find((m) => m.id === method)!;
const earliest = nextBusinessDay(today, selected.minDays);
const latest = nextBusinessDay(
today,
selected.minDays + (method === "standard" ? 2 : 1),
);
const handleMethodChange = (id: ShippingMethod) => {
setMethod(id);
setDate(undefined);
};
const deliveryStart = date ?? earliest;
const deliveryEnd = date
? addDays(date, method === "standard" ? 2 : 1)
: latest;
return (
<div className="w-full max-w-sm space-y-4 rounded-xl border bg-background p-4 shadow-sm">
<div className="flex items-center gap-2">
<PackageIcon
aria-hidden="true"
className="size-4 text-muted-foreground"
/>
<h3 className="font-semibold text-sm">Choose Delivery</h3>
</div>
<div className="grid grid-cols-2 gap-2">
{METHODS.map((m) => (
<button
className={cn(
"flex flex-col items-start rounded-lg border p-3 text-left transition-colors",
method === m.id
? "border-primary bg-primary/5"
: "border-input hover:bg-accent",
)}
key={m.id}
onClick={() => handleMethodChange(m.id)}
type="button"
>
<div className="flex w-full items-center justify-between">
<m.icon
aria-hidden="true"
className="size-4 text-muted-foreground"
/>
<Badge variant={m.id === "express" ? "info" : "outline"}>
{m.price}
</Badge>
</div>
<p className="mt-2 font-medium text-sm">{m.label}</p>
<p className="text-muted-foreground text-xs">{m.description}</p>
</button>
))}
</div>
<Popover>
<PopoverTrigger
render={<Button className="w-full justify-start" variant="outline" />}
>
<PackageIcon aria-hidden="true" />
{date
? `Deliver on ${format(date, "EEE, MMM dd")}`
: `Earliest: ${format(earliest, "EEE, MMM dd")}`}
</PopoverTrigger>
<PopoverPopup>
<Calendar
disabled={[
{ before: earliest },
{ after: addDays(today, 30) },
...(method === "standard"
? [{ dayOfWeek: [0, 6] as (0 | 1 | 2 | 3 | 4 | 5 | 6)[] }]
: []),
]}
mode="single"
onSelect={setDate}
selected={date}
/>
</PopoverPopup>
</Popover>
<p className="text-center text-muted-foreground text-xs">
Estimated delivery:{" "}
<span className="font-medium text-foreground">
{format(deliveryStart, "EEE, MMM dd")} –{" "}
{format(deliveryEnd, "EEE, MMM dd")}
</span>
</p>
<Button className="w-full">Confirm Delivery</Button>
</div>
);
}
Task Deadline Reschedule
A project task card with assignee avatars and a protected date picker — the calendar requires explicit Apply before the deadline updates, preventing accidental changes; a Rescheduled badge and the original date appear once a new deadline is set.
Design System Audit
Acme Corp · Sprint 4
Assignees
Due date
"use client";
import { addDays, format, isSameDay } from "date-fns";
import { CalendarIcon, ClipboardListIcon } from "lucide-react";
import { useState } from "react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
const ASSIGNEES = [
{ id: 1, initials: "AP", name: "Alice Park" },
{ id: 2, initials: "BT", name: "Ben Torres" },
{ id: 3, initials: "CK", name: "Celia Kim" },
];
const ORIGINAL_DEADLINE = addDays(new Date(), 5);
export default function Particle() {
const [deadline, setDeadline] = useState<Date>(ORIGINAL_DEADLINE);
const [pending, setPending] = useState<Date | undefined>();
const [open, setOpen] = useState(false);
const [saved, setSaved] = useState(false);
const isRescheduled = !isSameDay(deadline, ORIGINAL_DEADLINE);
const handleOpenChange = (isOpen: boolean) => {
if (isOpen) setPending(deadline);
setOpen(isOpen);
};
const handleApply = () => {
if (pending) setDeadline(pending);
setSaved(false);
setOpen(false);
};
const handleCancel = () => {
setPending(deadline);
setOpen(false);
};
return (
<div className="w-full max-w-sm space-y-4 rounded-xl border bg-background p-4 shadow-sm">
<div className="flex items-start gap-3 border-b pb-4">
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-primary/10">
<ClipboardListIcon
aria-hidden="true"
className="size-4 text-primary"
/>
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<p className="font-semibold text-sm">Design System Audit</p>
{isRescheduled && (
<Badge size="sm" variant="warning">
Rescheduled
</Badge>
)}
</div>
<p className="text-muted-foreground text-xs">Acme Corp · Sprint 4</p>
</div>
</div>
<div className="space-y-1">
<p className="font-medium text-muted-foreground text-xs">Assignees</p>
<div className="flex items-center gap-1.5">
{ASSIGNEES.map((a) => (
<Avatar className="size-7 ring-2 ring-background" key={a.id}>
<AvatarFallback className="text-[10px]">
{a.initials}
</AvatarFallback>
</Avatar>
))}
</div>
</div>
<div className="space-y-1.5">
<p className="font-medium text-muted-foreground text-xs">Due date</p>
<Popover onOpenChange={handleOpenChange} open={open}>
<PopoverTrigger
render={
<Button className="w-full justify-start" variant="outline" />
}
>
<CalendarIcon aria-hidden="true" />
{format(deadline, "EEE, MMM dd, yyyy")}
</PopoverTrigger>
<PopoverPopup>
<Calendar
defaultMonth={pending}
disabled={{ before: new Date() }}
mode="single"
onSelect={setPending}
selected={pending}
/>
<div className="flex items-center justify-end gap-2 border-t p-3">
<Button onClick={handleCancel} size="sm" variant="ghost">
Cancel
</Button>
<Button
disabled={
!pending || (!!pending && isSameDay(pending, deadline))
}
onClick={handleApply}
size="sm"
>
Apply
</Button>
</div>
</PopoverPopup>
</Popover>
{isRescheduled && (
<p className="text-muted-foreground text-xs">
Original:{" "}
<span className="line-through">
{format(ORIGINAL_DEADLINE, "MMM dd, yyyy")}
</span>
</p>
)}
</div>
<Button
className="w-full"
disabled={saved || !isRescheduled}
onClick={() => setSaved(true)}
>
{saved ? "Changes Saved" : "Save Changes"}
</Button>
</div>
);
}
Report Period Range with Presets
Quick-select month and multi-month preset buttons (This month, Next month, Next 3 months, Next 6 months) paired with a 2-month range calendar and a day-count badge in the trigger.
"use client";
import { addMonths, endOfMonth, format, startOfMonth } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
const today = new Date();
const PRESETS = [
{ from: startOfMonth(today), label: "This month", to: endOfMonth(today) },
{
from: startOfMonth(addMonths(today, 1)),
label: "Next month",
to: endOfMonth(addMonths(today, 1)),
},
{ from: today, label: "Next 3 months", to: endOfMonth(addMonths(today, 2)) },
{ from: today, label: "Next 6 months", to: endOfMonth(addMonths(today, 5)) },
];
import type { DateRange } from "react-day-picker";
export default function Particle() {
const [range, setRange] = useState<DateRange | undefined>();
const [open, setOpen] = useState(false);
const label =
range?.from && range?.to
? `${format(range.from, "MMM d")} – ${format(range.to, "MMM d, yyyy")}`
: "Select report period";
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<div className="flex flex-wrap gap-1.5">
{PRESETS.map((p) => (
<button
className="rounded-full border px-3 py-0.5 font-medium text-muted-foreground text-xs transition-colors hover:border-foreground/30 hover:text-foreground"
key={p.label}
onClick={() => {
setRange({ from: p.from, to: p.to });
setOpen(false);
}}
type="button"
>
{p.label}
</button>
))}
</div>
<Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger
render={<Button className="w-full justify-start" variant="outline" />}
>
<CalendarIcon />
{label}
{range?.from && range?.to && (
<Badge className="ml-auto" size="sm" variant="secondary">
{Math.round(
(range.to.getTime() - range.from.getTime()) / 86400000,
) + 1}
d
</Badge>
)}
</PopoverTrigger>
<PopoverPopup>
<Calendar
mode="range"
numberOfMonths={2}
onSelect={setRange}
selected={range}
/>
<div className="flex justify-end gap-2 border-t p-3">
<Button
onClick={() => {
setRange(undefined);
setOpen(false);
}}
size="sm"
variant="ghost"
>
Clear
</Button>
<Button
disabled={!range?.from || !range?.to}
onClick={() => setOpen(false)}
size="sm"
>
Apply
</Button>
</div>
</PopoverPopup>
</Popover>
</div>
);
}
Recurring Event Date Picker
A single date picker combined with recurrence pill buttons (Does not repeat, Daily, Weekly, Monthly) that outputs a human-readable schedule summary below.
"use client";
import { format } from "date-fns";
import { CalendarIcon, RepeatIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
type Recurrence = "none" | "daily" | "weekly" | "monthly";
const OPTIONS: { id: Recurrence; label: string }[] = [
{ id: "none", label: "Does not repeat" },
{ id: "daily", label: "Daily" },
{ id: "weekly", label: "Weekly" },
{ id: "monthly", label: "Monthly" },
];
export default function Particle() {
const [date, setDate] = useState<Date | undefined>();
const [recurrence, setRecurrence] = useState<Recurrence>("none");
const [open, setOpen] = useState(false);
const summaryParts = [
date ? format(date, "EEE, MMM d, yyyy") : null,
recurrence !== "none"
? OPTIONS.find((o) => o.id === recurrence)?.label
: null,
].filter(Boolean);
return (
<div className="w-full max-w-xs space-y-3">
<Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger
render={<Button className="w-full justify-start" variant="outline" />}
>
<CalendarIcon />
{date ? format(date, "MMM d, yyyy") : "Pick a date"}
</PopoverTrigger>
<PopoverPopup>
<Calendar
disabled={{ before: new Date() }}
mode="single"
onSelect={(d) => {
setDate(d);
setOpen(false);
}}
selected={date}
/>
</PopoverPopup>
</Popover>
<div className="flex items-center gap-2">
<RepeatIcon className="size-4 shrink-0 text-muted-foreground" />
<div className="flex flex-wrap gap-1.5">
{OPTIONS.map((opt) => (
<button
className={`rounded-full border px-3 py-0.5 font-medium text-xs transition-colors ${
recurrence === opt.id
? "border-primary bg-primary text-primary-foreground"
: "text-muted-foreground hover:border-foreground/30"
}`}
key={opt.id}
onClick={() => setRecurrence(opt.id)}
type="button"
>
{opt.label}
</button>
))}
</div>
</div>
{summaryParts.length > 0 && (
<p className="text-muted-foreground text-xs">
Scheduled:{" "}
<span className="font-medium text-foreground">
{summaryParts.join(" · ")}
</span>
</p>
)}
</div>
);
}
Hotel Stay Range with Duration Shortcuts
Duration shortcut buttons (1 week, 2 weeks, 1 month) snap the range instantly; the trigger badge shows the night count and a Clear / Done footer controls the popover.
Plan your stay
"use client";
import { addDays, format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";
import type { DateRange } from "react-day-picker";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
const DURATIONS = [
{ label: "1 week", nights: 7 },
{ label: "2 weeks", nights: 14 },
{ label: "1 month", nights: 30 },
];
export default function Particle() {
const [range, setRange] = useState<DateRange | undefined>();
const [open, setOpen] = useState(false);
const today = new Date();
const nights =
range?.from && range?.to
? Math.round((range.to.getTime() - range.from.getTime()) / 86400000)
: null;
return (
<div className="w-full max-w-sm space-y-3 rounded-xl border bg-background p-4 shadow-sm">
<p className="font-semibold text-sm">Plan your stay</p>
<div className="flex gap-1.5">
{DURATIONS.map((d) => (
<button
className="flex-1 rounded-md border py-1.5 font-medium text-muted-foreground text-xs transition-colors hover:border-primary hover:text-primary"
key={d.label}
onClick={() =>
setRange({ from: today, to: addDays(today, d.nights) })
}
type="button"
>
{d.label}
</button>
))}
</div>
<Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger
render={<Button className="w-full justify-start" variant="outline" />}
>
<CalendarIcon />
{range?.from && range?.to
? `${format(range.from, "MMM d")} – ${format(range.to, "MMM d, yyyy")}`
: "Select check-in / check-out"}
{nights && (
<Badge className="ml-auto" size="sm" variant="secondary">
{nights}n
</Badge>
)}
</PopoverTrigger>
<PopoverPopup>
<Calendar
disabled={{ before: today }}
mode="range"
numberOfMonths={2}
onSelect={setRange}
selected={range}
/>
<div className="flex items-center justify-between border-t px-4 py-3">
<p className="text-muted-foreground text-xs">
{nights
? `${nights} night${nights !== 1 ? "s" : ""}`
: "Select dates"}
</p>
<div className="flex gap-2">
<Button
onClick={() => {
setRange(undefined);
}}
size="sm"
variant="ghost"
>
Clear
</Button>
<Button
disabled={!range?.from || !range?.to}
onClick={() => setOpen(false)}
size="sm"
>
Done
</Button>
</div>
</div>
</PopoverPopup>
</Popover>
</div>
);
}
Birthday Picker with Age Validation
A dropdown-navigation calendar scoped to the 18–100 year age window, auto-closing on selection and displaying the computed age below the input.
Date of birth
You must be 18–100 years old to register.
"use client";
import { format, subYears } from "date-fns";
import { CalendarIcon, ChevronDownIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
const today = new Date();
const MIN_AGE = 18;
const MAX_AGE = 100;
const minDate = subYears(today, MAX_AGE);
const maxDate = subYears(today, MIN_AGE);
export default function Particle() {
const [dob, setDob] = useState<Date | undefined>();
const [open, setOpen] = useState(false);
const age = dob
? Math.floor(
(today.getTime() - dob.getTime()) / (1000 * 60 * 60 * 24 * 365.25),
)
: null;
return (
<div className="w-full max-w-xs space-y-3">
<div className="space-y-1">
<p className="font-medium text-sm">Date of birth</p>
<p className="text-muted-foreground text-xs">
You must be 18–100 years old to register.
</p>
</div>
<Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger
render={
<Button className="w-full justify-between" variant="outline" />
}
>
<span className="flex items-center gap-2">
<CalendarIcon className="size-4 text-muted-foreground" />
{dob ? format(dob, "MMMM d, yyyy") : "Select your birthday"}
</span>
<ChevronDownIcon className="size-4 text-muted-foreground" />
</PopoverTrigger>
<PopoverPopup>
<Calendar
captionLayout="dropdown"
disabled={[{ before: minDate }, { after: maxDate }]}
endMonth={maxDate}
mode="single"
onSelect={(d) => {
setDob(d);
if (d) setOpen(false);
}}
selected={dob}
startMonth={minDate}
/>
</PopoverPopup>
</Popover>
{age !== null && (
<p className="rounded-md border border-dashed px-3 py-2 text-muted-foreground text-xs">
Age:{" "}
<span className="font-semibold text-foreground">{age} years old</span>
</p>
)}
</div>
);
}
Multi-date Availability Selector
A multiple-selection calendar for picking available days, with removable chip tags below the trigger and a night-count badge summarising the selection.
Available days
Select the days you are available for meetings.
"use client";
import { format, isSameDay } from "date-fns";
import { CalendarIcon, XIcon } from "lucide-react";
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverPopup,
PopoverTrigger,
} from "@/components/ui/popover";
export default function Particle() {
const [dates, setDates] = useState<Date[]>([]);
const [open, setOpen] = useState(false);
const today = new Date();
const toggleDate = (d: Date) => {
setDates((prev) =>
prev.some((p) => isSameDay(p, d))
? prev.filter((p) => !isSameDay(p, d))
: [...prev, d].sort((a, b) => a.getTime() - b.getTime()),
);
};
return (
<div className="w-full max-w-sm space-y-3">
<div className="space-y-1">
<p className="font-medium text-sm">Available days</p>
<p className="text-muted-foreground text-xs">
Select the days you are available for meetings.
</p>
</div>
<Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger
render={<Button className="w-full justify-start" variant="outline" />}
>
<CalendarIcon />
{dates.length === 0
? "Pick available days"
: `${dates.length} day${dates.length !== 1 ? "s" : ""} selected`}
{dates.length > 0 && (
<Badge className="ml-auto" size="sm" variant="secondary">
{dates.length}
</Badge>
)}
</PopoverTrigger>
<PopoverPopup>
<Calendar
disabled={{ before: today }}
mode="multiple"
onSelect={(days) => setDates(days ?? [])}
selected={dates}
/>
<div className="flex justify-end gap-2 border-t p-3">
<Button onClick={() => setDates([])} size="sm" variant="ghost">
Clear
</Button>
<Button onClick={() => setOpen(false)} size="sm">
Done
</Button>
</div>
</PopoverPopup>
</Popover>
{dates.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{dates.map((d) => (
<span
className="flex items-center gap-1 rounded-full border bg-muted px-2.5 py-0.5 font-medium text-xs"
key={d.toISOString()}
>
{format(d, "MMM d")}
<button
aria-label={`Remove ${format(d, "MMM d")}`}
className="rounded-full text-muted-foreground hover:text-foreground"
onClick={() => toggleDate(d)}
type="button"
>
<XIcon className="size-3" />
</button>
</span>
))}
</div>
)}
</div>
);
}
On This Page

