Date Picker
React date picker with calendar popup, keyboard navigation, and date formatting. Built with Popover and Calendar components for forms and scheduling.
Need users to pick dates for forms, bookings, or scheduling? This date picker combines a clean button trigger with a full calendar popup. Perfect for birthdays, appointments, deadlines - any time you need date input that actually works.
"use client"import * as React from "react"import { format } from "date-fns"import { Calendar as CalendarIcon } from "lucide-react"import { cn } from "@/lib/utils"import { Button } from "@/components/ui/button"import { Calendar } from "@/components/ui/calendar"import { Popover, PopoverContent, PopoverTrigger,} from "@/components/ui/popover"export default function DatePickerDemo() { const [date, setDate] = React.useState<Date>() return ( <div className="w-full p-6 flex justify-center"> <Popover> <PopoverTrigger asChild> <Button variant="outline" data-empty={!date} className="data-[empty=true]:text-muted-foreground w-[280px] justify-start text-left font-normal" > <CalendarIcon /> {date ? format(date, "PPP") : <span>Pick a date</span>} </Button> </PopoverTrigger> <PopoverContent className="w-auto p-0"> <Calendar mode="single" selected={date} onSelect={setDate} /> </PopoverContent> </Popover> </div> )}
import * as React from "react"import { Slot } from "@radix-ui/react-slot"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "@/lib/utils"const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, })function Button({ className, variant, size, asChild = false, ...props}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> )}export { Button, buttonVariants }
Built by combining shadcn/ui's Popover and Calendar components with some date formatting magic.
npx shadcn@latest add popover calendar
npm install date-fns
Why developers choose this date picker
Built on React DayPicker with shadcn/ui components:
- Smart popup positioning - Never gets cut off by container edges
- Keyboard shortcuts - Arrow keys navigate, Enter selects, Escape closes
- Mobile friendly - Touch-optimized calendar that works on any device
- Date range support - Single dates, ranges, or multiple date selection
- Flexible formatting - Display dates however your users expect
- Form integration - Works seamlessly with React Hook Form and validation
Date picker patterns you'll actually use
Birthday and date of birth forms
Year/month dropdowns make it easy to pick dates way in the past:
"use client"import * as React from "react"import { ChevronDownIcon } from "lucide-react"import { Button } from "@/components/ui/button"import { Calendar } from "@/components/ui/calendar"import { Label } from "@/components/ui/label"import { Popover, PopoverContent, PopoverTrigger,} from "@/components/ui/popover"export default function DatePickerDateOfBirth() { const [open, setOpen] = React.useState(false) const [date, setDate] = React.useState<Date | undefined>(undefined) return ( <div className="w-full p-6 flex justify-center"> <div className="flex flex-col gap-3"> <Label htmlFor="date" className="px-1"> Date of birth </Label> <Popover open={open} onOpenChange={setOpen}> <PopoverTrigger asChild> <Button variant="outline" id="date" className="w-48 justify-between font-normal" > {date ? date.toLocaleDateString() : "Select date"} <ChevronDownIcon /> </Button> </PopoverTrigger> <PopoverContent className="w-auto overflow-hidden p-0" align="start"> <Calendar mode="single" selected={date} captionLayout="dropdown" onSelect={(date) => { setDate(date) setOpen(false) }} /> </PopoverContent> </Popover> </div> </div> )}
import * as React from "react"import { Slot } from "@radix-ui/react-slot"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "@/lib/utils"const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, })function Button({ className, variant, size, asChild = false, ...props}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> )}export { Button, buttonVariants }
Text input with calendar button
Let users type dates or click the calendar - covers all the bases:
"use client"import * as React from "react"import { CalendarIcon } from "lucide-react"import { Button } from "@/components/ui/button"import { Calendar } from "@/components/ui/calendar"import { Input } from "@/components/ui/input"import { Label } from "@/components/ui/label"import { Popover, PopoverContent, PopoverTrigger,} from "@/components/ui/popover"function formatDate(date: Date | undefined) { if (!date) { return "" } return date.toLocaleDateString("en-US", { day: "2-digit", month: "long", year: "numeric", })}function isValidDate(date: Date | undefined) { if (!date) { return false } return !isNaN(date.getTime())}export default function DatePickerSubscription() { const [open, setOpen] = React.useState(false) const [date, setDate] = React.useState<Date | undefined>( new Date("2025-06-01") ) const [month, setMonth] = React.useState<Date | undefined>(date) const [value, setValue] = React.useState(formatDate(date)) return ( <div className="w-full p-6 flex justify-center"> <div className="flex flex-col gap-3 w-full max-w-sm"> <Label htmlFor="date" className="px-1"> Subscription Date </Label> <div className="relative flex gap-2"> <Input id="date" value={value} placeholder="June 01, 2025" className="bg-background pr-10" onChange={(e) => { const date = new Date(e.target.value) setValue(e.target.value) if (isValidDate(date)) { setDate(date) setMonth(date) } }} onKeyDown={(e) => { if (e.key === "ArrowDown") { e.preventDefault() setOpen(true) } }} /> <Popover open={open} onOpenChange={setOpen}> <PopoverTrigger asChild> <Button id="date-picker" variant="ghost" className="absolute top-1/2 right-2 size-6 -translate-y-1/2" > <CalendarIcon className="size-3.5" /> <span className="sr-only">Select date</span> </Button> </PopoverTrigger> <PopoverContent className="w-auto overflow-hidden p-0" align="end" alignOffset={-8} sideOffset={10} > <Calendar mode="single" selected={date} captionLayout="dropdown" month={month} onMonthChange={setMonth} onSelect={(date) => { setDate(date) setValue(formatDate(date)) setOpen(false) }} /> </PopoverContent> </Popover> </div> </div> </div> )}
import * as React from "react"import { Slot } from "@radix-ui/react-slot"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "@/lib/utils"const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, })function Button({ className, variant, size, asChild = false, ...props}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> )}export { Button, buttonVariants }
Date and time scheduling
Perfect for appointment booking and event planning:
"use client"import * as React from "react"import { ChevronDownIcon } from "lucide-react"import { Button } from "@/components/ui/button"import { Calendar } from "@/components/ui/calendar"import { Input } from "@/components/ui/input"import { Label } from "@/components/ui/label"import { Popover, PopoverContent, PopoverTrigger,} from "@/components/ui/popover"export default function CalendarDateTimePicker() { const [open, setOpen] = React.useState(false) const [date, setDate] = React.useState<Date | undefined>(undefined) return ( <div className="w-full p-6 flex justify-center"> <div className="flex gap-4"> <div className="flex flex-col gap-3"> <Label htmlFor="date-picker" className="px-1"> Date </Label> <Popover open={open} onOpenChange={setOpen}> <PopoverTrigger asChild> <Button variant="outline" id="date-picker" className="w-32 justify-between font-normal" > {date ? date.toLocaleDateString() : "Select date"} <ChevronDownIcon /> </Button> </PopoverTrigger> <PopoverContent className="w-auto overflow-hidden p-0" align="start"> <Calendar mode="single" selected={date} captionLayout="dropdown" onSelect={(date) => { setDate(date) setOpen(false) }} /> </PopoverContent> </Popover> </div> <div className="flex flex-col gap-3"> <Label htmlFor="time-picker" className="px-1"> Time </Label> <Input type="time" id="time-picker" step="1" defaultValue="10:30:00" className="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none" /> </div> </div> </div> )}
import * as React from "react"import { Slot } from "@radix-ui/react-slot"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "@/lib/utils"const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, })function Button({ className, variant, size, asChild = false, ...props}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> )}export { Button, buttonVariants }
Smart date parsing
Users can type "tomorrow", "next Friday", "in 2 weeks" - no calendar clicking required:
"use client"import * as React from "react"import { CalendarIcon } from "lucide-react"import { Button } from "@/components/ui/button"import { Calendar } from "@/components/ui/calendar"import { Input } from "@/components/ui/input"import { Label } from "@/components/ui/label"import { Popover, PopoverContent, PopoverTrigger,} from "@/components/ui/popover"// Simple natural language date parser (for demo purposes)function parseDate(input: string): Date | null { const today = new Date() const tomorrow = new Date(today) tomorrow.setDate(tomorrow.getDate() + 1) const nextWeek = new Date(today) nextWeek.setDate(nextWeek.getDate() + 7) const lowerInput = input.toLowerCase().trim() if (lowerInput === "today") return today if (lowerInput === "tomorrow") return tomorrow if (lowerInput === "next week") return nextWeek if (lowerInput === "in 2 days") { const date = new Date(today) date.setDate(date.getDate() + 2) return date } // Try to parse as regular date const parsed = new Date(input) return isNaN(parsed.getTime()) ? null : parsed}function formatDate(date: Date | undefined) { if (!date) { return "" } return date.toLocaleDateString("en-US", { day: "2-digit", month: "long", year: "numeric", })}export default function DatePickerSchedule() { const [open, setOpen] = React.useState(false) const [value, setValue] = React.useState("In 2 days") const [date, setDate] = React.useState<Date | undefined>( parseDate("In 2 days") || undefined ) const [month, setMonth] = React.useState<Date | undefined>(date) return ( <div className="w-full p-6 flex justify-center"> <div className="flex flex-col gap-3 w-full max-w-sm"> <Label htmlFor="date" className="px-1"> Schedule Date </Label> <div className="relative flex gap-2"> <Input id="date" value={value} placeholder="Tomorrow or next week" className="bg-background pr-10" onChange={(e) => { setValue(e.target.value) const date = parseDate(e.target.value) if (date) { setDate(date) setMonth(date) } }} onKeyDown={(e) => { if (e.key === "ArrowDown") { e.preventDefault() setOpen(true) } }} /> <Popover open={open} onOpenChange={setOpen}> <PopoverTrigger asChild> <Button id="date-picker" variant="ghost" className="absolute top-1/2 right-2 size-6 -translate-y-1/2" > <CalendarIcon className="size-3.5" /> <span className="sr-only">Select date</span> </Button> </PopoverTrigger> <PopoverContent className="w-auto overflow-hidden p-0" align="end"> <Calendar mode="single" selected={date} captionLayout="dropdown" month={month} onMonthChange={setMonth} onSelect={(date) => { setDate(date) setValue(formatDate(date)) setOpen(false) }} /> </PopoverContent> </Popover> </div> <div className="text-muted-foreground px-1 text-sm"> Your post will be published on{" "} <span className="font-medium">{formatDate(date)}</span>. </div> </div> </div> )}
import * as React from "react"import { Slot } from "@radix-ui/react-slot"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "@/lib/utils"const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, })function Button({ className, variant, size, asChild = false, ...props}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> )}export { Button, buttonVariants }
Form validation made easy
Integrates perfectly with React Hook Form, Zod validation, and error handling:
"use client"import { zodResolver } from "@hookform/resolvers/zod"import { format } from "date-fns"import { CalendarIcon } from "lucide-react"import { useForm } from "react-hook-form"import { toast } from "sonner"import { z } from "zod"import { cn } from "@/lib/utils"import { Button } from "@/components/ui/button"import { Calendar } from "@/components/ui/calendar"import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage,} from "@/components/ui/form"import { Popover, PopoverContent, PopoverTrigger,} from "@/components/ui/popover"const FormSchema = z.object({ dob: z.date({ required_error: "A date of birth is required.", }),})export default function DatePickerForm() { const form = useForm<z.infer<typeof FormSchema>>({ resolver: zodResolver(FormSchema), }) function onSubmit(data: z.infer<typeof FormSchema>) { toast("You submitted the following values", { description: ( <pre className="mt-2 w-[320px] rounded-md bg-neutral-950 p-4"> <code className="text-white">{JSON.stringify(data, null, 2)}</code> </pre> ), }) } return ( <div className="w-full p-6 flex justify-center"> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> <FormField control={form.control} name="dob" render={({ field }) => ( <FormItem className="flex flex-col"> <FormLabel>Date of birth</FormLabel> <Popover> <PopoverTrigger asChild> <FormControl> <Button variant={"outline"} className={cn( "w-[240px] pl-3 text-left font-normal", !field.value && "text-muted-foreground" )} > {field.value ? ( format(field.value, "PPP") ) : ( <span>Pick a date</span> )} <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> </Button> </FormControl> </PopoverTrigger> <PopoverContent className="w-auto p-0" align="start"> <Calendar mode="single" selected={field.value} onSelect={field.onChange} disabled={(date) => date > new Date() || date < new Date("1900-01-01") } captionLayout="dropdown" /> </PopoverContent> </Popover> <FormDescription> Your date of birth is used to calculate your age. </FormDescription> <FormMessage /> </FormItem> )} /> <Button type="submit">Submit</Button> </form> </Form> </div> )}
import * as React from "react"import { Slot } from "@radix-ui/react-slot"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "@/lib/utils"const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default", }, })function Button({ className, variant, size, asChild = false, ...props}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean }) { const Comp = asChild ? Slot : "button" return ( <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> )}export { Button, buttonVariants }
What props can you customize?
Calendar Props
The main calendar component gives you tons of control:
Prop | Type | Default | What it does |
---|---|---|---|
mode | "single" | "multiple" | "range" | "single" | Pick one date, multiple dates, or a range |
selected | Date | Date[] | DateRange | - | Which date(s) are currently selected |
onSelect | (date: Date | undefined) => void | - | What happens when user picks a date |
disabled | Matcher | Matcher[] | - | Which dates can't be clicked |
captionLayout | "buttons" | "dropdown" | "dropdown-months" | "dropdown-years" | "buttons" | How month/year navigation looks |
fromDate | Date | - | Earliest date users can pick |
toDate | Date | - | Latest date users can pick |
defaultMonth | Date | - | Which month shows up first |
numberOfMonths | number | 1 | Show multiple months at once |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 1 | Monday or Sunday first? |
showOutsideDays | boolean | false | Show grayed-out days from other months |
Popover Props
Controls the floating calendar popup:
Prop | Type | Default | What it does |
---|---|---|---|
open | boolean | - | Force the calendar open/closed |
onOpenChange | (open: boolean) => void | - | Know when calendar opens/closes |
modal | boolean | true | Block clicks outside? Usually yes |
PopoverContent Props
Position the calendar exactly where you want it:
Prop | Type | Default | What it does |
---|---|---|---|
align | "start" | "center" | "end" | "center" | Left, center, or right aligned |
sideOffset | number | 4 | Space between button and calendar |
alignOffset | number | 0 | Fine-tune the positioning |
className | string | - | Custom styling |
Date picker best practices
What works best in real applications:
- Always label your date inputs - Screen readers and confused users will thank you
- Keep date formats consistent - Pick MM/dd/yyyy or dd/MM/yyyy and stick with it
- Disable impossible dates - No booking appointments in the past, no birthdays in the future
- Show what format you expect - Placeholder text like "MM/DD/YYYY" prevents frustration
- Handle timezones explicitly - Don't let date boundaries surprise your users
- Test with keyboard only - Tab through everything, make sure it all works
- Consider mobile users - Native date inputs might be better on phones
Data Table
React data table with sorting, filtering, pagination, and selection. Built with TanStack Table for powerful data management and user interactions.
Dialog
React dialog component with focus trapping, keyboard navigation, and animations. Perfect for forms, confirmations, and modals that need user attention.