Examples
Basic
A labelled date field that opens a single-date calendar.
import { Button } from "@codefast/ui/button";
import { Calendar } from "@codefast/ui/calendar";
import { Field, FieldLabel } from "@codefast/ui/field";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { useState } from "react";
function formatLong(date: Date): string {
return date.toLocaleDateString("en-US", { day: "numeric", month: "long", year: "numeric" });
}
export function DatePickerBasic() {
const [date, setDate] = useState<Date>();
return (
<Field className="mx-auto w-44">
<FieldLabel htmlFor="date-picker-basic">Date</FieldLabel>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" id="date-picker-basic" className="justify-start font-normal">
{date ? formatLong(date) : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="w-auto p-0">
<Calendar mode="single" selected={date} onSelect={setDate} {...(date ? { defaultMonth: date } : {})} />
</PopoverContent>
</Popover>
</Field>
);
}
Range Picker
Select a start and end date across two months with mode=range.
import { Button } from "@codefast/ui/button";
import { Calendar } from "@codefast/ui/calendar";
import { Field, FieldLabel } from "@codefast/ui/field";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";
/** Structural match for @daypicker/react's DateRange (kept local to avoid a transitive import). */
interface DateRange {
from: Date | undefined;
to?: Date | undefined;
}
function addDays(date: Date, days: number): Date {
const next = new Date(date);
next.setDate(next.getDate() + days);
return next;
}
function formatShort(date: Date): string {
return date.toLocaleDateString("en-US", { month: "short", day: "2-digit", year: "numeric" });
}
const initialFrom = new Date(new Date().getFullYear(), 0, 20);
export function DatePickerRange() {
const [date, setDate] = useState<DateRange | undefined>({
from: initialFrom,
to: addDays(initialFrom, 20),
});
return (
<Field className="mx-auto w-60">
<FieldLabel htmlFor="date-picker-range">Date Picker Range</FieldLabel>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" id="date-picker-range" className="justify-start px-2.5 font-normal">
<CalendarIcon />
{date?.from ? (
date.to ? (
<>
{formatShort(date.from)} - {formatShort(date.to)}
</>
) : (
formatShort(date.from)
)
) : (
<span>Pick a date</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="w-auto p-0">
<Calendar
mode="range"
defaultMonth={date?.from ?? initialFrom}
selected={date}
onSelect={setDate}
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
</Field>
);
}
Date of Birth
A dropdown caption lets users jump to a distant month or year quickly.
import { Button } from "@codefast/ui/button";
import { Calendar } from "@codefast/ui/calendar";
import { Field, FieldLabel } from "@codefast/ui/field";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { useState } from "react";
export function DatePickerDob() {
const [open, setOpen] = useState(false);
const [date, setDate] = useState<Date | undefined>(undefined);
return (
<Field className="mx-auto w-44">
<FieldLabel htmlFor="date-of-birth">Date of birth</FieldLabel>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="outline" id="date-of-birth" className="justify-start font-normal">
{date ? date.toLocaleDateString() : "Select date"}
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="w-auto overflow-hidden p-0">
<Calendar
mode="single"
selected={date}
captionLayout="dropdown"
{...(date ? { defaultMonth: date } : {})}
onSelect={(value) => {
setDate(value);
setOpen(false);
}}
/>
</PopoverContent>
</Popover>
</Field>
);
}
Input
Type a date in the field or pick it from the calendar — the two stay in sync.
import { Calendar } from "@codefast/ui/calendar";
import { Field, FieldLabel } from "@codefast/ui/field";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@codefast/ui/input-group";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";
function formatDate(date: Date | undefined): string {
if (!date) {
return "";
}
return date.toLocaleDateString("en-US", { day: "2-digit", month: "long", year: "numeric" });
}
function isValidDate(date: Date | undefined): boolean {
if (!date) {
return false;
}
return !Number.isNaN(date.getTime());
}
const initialDate = new Date("2025-06-01");
export function DatePickerInput() {
const [open, setOpen] = useState(false);
const [date, setDate] = useState<Date | undefined>(initialDate);
const [month, setMonth] = useState<Date>(initialDate);
const [value, setValue] = useState(formatDate(initialDate));
return (
<Field className="mx-auto w-48">
<FieldLabel htmlFor="date-input">Subscription Date</FieldLabel>
<InputGroup>
<InputGroupInput
id="date-input"
value={value}
placeholder="June 01, 2025"
onChange={(event) => {
const next = new Date(event.target.value);
setValue(event.target.value);
if (isValidDate(next)) {
setDate(next);
setMonth(next);
}
}}
onKeyDown={(event) => {
if (event.key === "ArrowDown") {
event.preventDefault();
setOpen(true);
}
}}
/>
<InputGroupAddon align="inline-end">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<InputGroupButton id="date-input-trigger" variant="ghost" size="icon-xs" aria-label="Select date">
<CalendarIcon />
<span className="sr-only">Select date</span>
</InputGroupButton>
</PopoverTrigger>
<PopoverContent align="end" alignOffset={-8} sideOffset={10} className="w-auto overflow-hidden p-0">
<Calendar
mode="single"
selected={date}
month={month}
onMonthChange={setMonth}
onSelect={(next) => {
setDate(next);
setValue(formatDate(next));
setOpen(false);
}}
/>
</PopoverContent>
</Popover>
</InputGroupAddon>
</InputGroup>
</Field>
);
}
Time Picker
Pair a date picker with a native time input.
import { Button } from "@codefast/ui/button";
import { Calendar } from "@codefast/ui/calendar";
import { Field, FieldGroup, FieldLabel } from "@codefast/ui/field";
import { Input } from "@codefast/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { ChevronDownIcon } from "lucide-react";
import { useState } from "react";
function formatLong(date: Date): string {
return date.toLocaleDateString("en-US", { day: "numeric", month: "long", year: "numeric" });
}
export function DatePickerTime() {
const [open, setOpen] = useState(false);
const [date, setDate] = useState<Date | undefined>(undefined);
return (
<FieldGroup className="mx-auto max-w-xs flex-row">
<Field>
<FieldLabel htmlFor="date-picker-time">Date</FieldLabel>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="outline" id="date-picker-time" className="w-32 justify-between font-normal">
{date ? formatLong(date) : "Select date"}
<ChevronDownIcon />
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="w-auto overflow-hidden p-0">
<Calendar
mode="single"
selected={date}
captionLayout="dropdown"
{...(date ? { defaultMonth: date } : {})}
onSelect={(value) => {
setDate(value);
setOpen(false);
}}
/>
</PopoverContent>
</Popover>
</Field>
<Field className="w-32">
<FieldLabel htmlFor="time-picker">Time</FieldLabel>
<Input
type="time"
id="time-picker"
step="1"
defaultValue="10:30:00"
className="appearance-none bg-background [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
/>
</Field>
</FieldGroup>
);
}
Natural Language
Parse phrases like “in 2 days” into a date with chrono-node.
import { Calendar } from "@codefast/ui/calendar";
import { Field, FieldLabel } from "@codefast/ui/field";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@codefast/ui/input-group";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { parseDate } from "chrono-node";
import { CalendarIcon } from "lucide-react";
import { useState } from "react";
function formatDate(date: Date | undefined): string {
if (!date) {
return "";
}
return date.toLocaleDateString("en-US", { day: "2-digit", month: "long", year: "numeric" });
}
export function DatePickerNaturalLanguage() {
const [open, setOpen] = useState(false);
const [value, setValue] = useState("In 2 days");
const [date, setDate] = useState<Date | undefined>(parseDate(value) ?? undefined);
return (
<Field className="mx-auto max-w-xs">
<FieldLabel htmlFor="date-natural">Schedule Date</FieldLabel>
<InputGroup>
<InputGroupInput
id="date-natural"
value={value}
placeholder="Tomorrow or next week"
onChange={(event) => {
setValue(event.target.value);
const next = parseDate(event.target.value);
if (next) {
setDate(next);
}
}}
onKeyDown={(event) => {
if (event.key === "ArrowDown") {
event.preventDefault();
setOpen(true);
}
}}
/>
<InputGroupAddon align="inline-end">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<InputGroupButton id="date-natural-trigger" variant="ghost" size="icon-xs" aria-label="Select date">
<CalendarIcon />
<span className="sr-only">Select date</span>
</InputGroupButton>
</PopoverTrigger>
<PopoverContent align="end" sideOffset={8} className="w-auto overflow-hidden p-0">
<Calendar
mode="single"
selected={date}
captionLayout="dropdown"
{...(date ? { defaultMonth: date } : {})}
onSelect={(next) => {
setDate(next);
setValue(formatDate(next));
setOpen(false);
}}
/>
</PopoverContent>
</Popover>
</InputGroupAddon>
</InputGroup>
<div className="px-1 text-sm text-muted-foreground">
Your post will be published on <span className="font-medium">{formatDate(date)}</span>.
</div>
</Field>
);
}
RTL
Right-to-left layout support for languages such as Arabic and Hebrew.
Translations are AI-generated for demonstration and may be imperfect.
import { Button } from "@codefast/ui/button";
import { Calendar } from "@codefast/ui/calendar";
import { Popover, PopoverContent, PopoverTrigger } from "@codefast/ui/popover";
import { arSA, he } from "@daypicker/react/locale";
import { ChevronDownIcon } from "lucide-react";
import { useState } from "react";
import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";
const translations: Translations = {
en: { dir: "ltr", values: { placeholder: "Pick a date" } },
ar: { dir: "rtl", values: { placeholder: "اختر تاريخًا" } },
he: { dir: "rtl", values: { placeholder: "בחר תاريخًا" } },
};
const dayPickerLocales = { ar: arSA, he } as const;
/** BCP-47 tag for Intl date formatting, derived from the active language. */
const intlLocales: Record<string, string> = { ar: "ar-SA", he: "he-IL" };
export function DatePickerRtl() {
const { dir, t, language } = useTranslation(translations, "ar");
const [date, setDate] = useState<Date>();
const dayPickerLocale = dir === "rtl" ? dayPickerLocales[language as keyof typeof dayPickerLocales] : undefined;
const intlLocale = dir === "rtl" ? (intlLocales[language] ?? "en-US") : "en-US";
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
dir={dir}
data-empty={!date}
className="w-53 justify-between text-start font-normal data-[empty=true]:text-muted-foreground"
>
{date ? (
date.toLocaleDateString(intlLocale, { day: "numeric", month: "long", year: "numeric" })
) : (
<span>{t.placeholder}</span>
)}
<ChevronDownIcon />
</Button>
</PopoverTrigger>
<PopoverContent align="start" dir={dir} className="w-auto p-0">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
dir={dir}
locale={dayPickerLocale}
{...(date ? { defaultMonth: date } : {})}
/>
</PopoverContent>
</Popover>
);
}
Anatomy
How the parts nest — every slot the component exposes, in composition order.
Features
- Composes Popover + Calendar — inherits their focus-trap and keyboard behaviour for free, nothing reimplemented.
- The demonstrated recipes cover a single date, a start/end range (mode="range"), a typed date kept in sync with the calendar, and natural-language parsing ("in 2 days") via chrono-node.
Guidelines
Conventions that keep usage consistent across an app.
Do
- Compose the picker from Popover + Calendar so it inherits their focus and keyboard behaviour.
- Mirror the calendar selection back into any text input so both stay in sync.
Don’t
- Don’t block typing — let users enter a date directly as well as pick one.
Explore further
Ready to integrate?
Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.