Date Picker

Used in this guide:
Next.js 16.0.1
Shadcn UI

1. Install Shadcn UI

We will install Shadcn UI in your existing Next.js project. But:

Bakcup your current globals.css first because Shadcn will replace it

npx shadcn@latest init

Make sure you get lib/utils.ts after the installation.

Modify root/components.json

root/components.json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "iconLibrary": "lucide",
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/shadcn/utils",
    "ui": "@/components/shadcn/ui",
    "lib": "@/lib/shadcn",
    "hooks": "@/hooks"
  },
  "registries": {}
}

After modifying the code above, do:

  • Create shadcn folder inside root/lib folder and move utils.ts there

So now when you add new Shadcn components, they will neatly stored in app/components/shadcn/ui




2. Install Date Picker Component


Add Popover Components

npx shadcn@latest add popover

Ref: ShadCN Popover


Add Calendar Components

npx shadcn@latest add calendar

Ref: ShadCN Calendar


Add Select Components

npx shadcn@latest add select

Ref: ShadCN Select




3. Create DatePicker Component


Restart VSCode first. You can use Ctrl + Shift + P -> type "restart" and choose "Developer: Restart Extension Host"

Now we have all the pieces required to assemble a customized DatePicker component.

app/components/shadcn/custom/DatePicker.tsx
"use client"
import { format, getMonth, getYear, setMonth, setYear } from "date-fns"
import { Calendar as CalendarIcon } from "lucide-react"
import { Calendar } from "@/components/shadcn/ui/calendar"
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from "@/components/shadcn/ui/popover"
import { useState } from "react"
import { Button } from "../ui/button"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
 
interface DatePickerProps {
    startYear?: number;
    endYear?: number;
    onDateChange: (date: Date) => void;
}
 
export function DatePicker({
    startYear = getYear(new Date()) - 120,
    endYear = getYear(new Date()) + 120,
    onDateChange,
}: DatePickerProps ) {
    const [date, setDate] = useState<Date>(new Date());
    const months = [
        "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
    ]
    const years = Array.from({ length: endYear - startYear + 1 }, (_, i) => startYear + i);
 
    const handleMonthChange = (month: string) => {
        const newDate = setMonth(date, months.indexOf(month));
        setDate(newDate);
    }
 
    const handleYearChange = (year: string) => {
        const newDate = setYear(date, parseInt(year));
        setDate(newDate);
    }
 
    const handleSelect = (selectedDate: Date | undefined) => {
        if (selectedDate) {
            setDate(selectedDate);
            onDateChange(selectedDate);
        }
    }
 
    return (
        <Popover>
            <PopoverTrigger asChild>
                <Button
                variant="outline"
                data-empty={!date}
                className="data-[empty=true]:text-muted-foreground w-[250px] justify-start text-left font-normal"
                >
                    <CalendarIcon />
                    {date ? format(date, "PPP") : <span>Pick a date</span>}
                </Button>
            </PopoverTrigger>
            <PopoverContent className="w-auto p-0">
                <div className="flex w-full gap-2 p-2">
                    
                    {/* Month options */}
                    <Select
                        onValueChange={handleMonthChange}
                        value={months[getMonth(date)]}
                    >
                        <SelectTrigger className="w-[50%]">
                            <SelectValue placeholder="Month" />
                        </SelectTrigger>
                        <SelectContent>
                            {months.map((month) => (
                                <SelectItem
                                    key={month}
                                    value={month}
                                    >{month}</SelectItem>
                            ))}
                        </SelectContent>
                    </Select>
 
                    {/* Year options */}
                    <Select
                        onValueChange={handleYearChange}
                        value={getYear(date).toString()}
                    >
                        <SelectTrigger className="w-[50%]">
                            <SelectValue placeholder="Year" />
                        </SelectTrigger>
                        <SelectContent className="max-h-[380px]">
                            {years.map((year) => (
                                <SelectItem
                                    key={year}
                                    value={year.toString()}
                                    >{year}</SelectItem>
                            ))}
                        </SelectContent>
                    </Select>
 
                </div>
                <Calendar 
                    mode="single" 
                    selected={date} 
                    onSelect={handleSelect} 
                    month={date}
                    onMonthChange={setDate}
                />
            </PopoverContent>
        </Popover>
    )
}

For demo code reference, visit: ShadCN Date Picker




4. Use DatePicker Component

Here is a very simple example how you can use DatePicker component in a page and lift state to use the value in another component.

app/components/BirthdayDisplay.tsx
interface BirthdayDisplayProps {
    birthday?: Date;
}
 
const BirthdayDisplay = ({ birthday }: BirthdayDisplayProps) => {
 
    const formattedBD = birthday?.toLocaleDateString("en-US", {
        month: "short",
        day: "numeric",
        year: "numeric",
    })
 
    return (
        <div className="mt-[600px]">
            {formattedBD ? (
                <div>
                    <p>My Birthday is <span className="font-bold">{formattedBD}</span></p>
                </div>
            ): (
                <div>
                    <p>You have not selected your birth date</p>
                </div>
            )}
        </div>
    )
}
 
export default BirthdayDisplay;
app/page.tsx
"use client"
import BirthdayDisplay from "@/components/BirthdayDisplay";
import { DatePicker } from "@/components/shadcn/custom/DatePicker";
import { useState } from "react";
 
export default function Home() {
 
  const [selectedDate, setSelectedDate] = useState<Date | undefined>();
 
  return (
    <div className="flex flex-col w-full items-center p-8 gap-8">
      <h1>Home Page</h1>
      <DatePicker onDateChange={setSelectedDate} />
      <BirthdayDisplay birthday={selectedDate} />
    </div>
  );
}

JKT

Stay focused, and the rest will follow

©Jakkrit Turner. All rights reserved