Custom Shadcn Date Selector for React and Tailwind CSS. A universal date selector component with multiple period types, filter modes, and flexible display options.
Browse 4 production-ready Shadcn Date Selector components for dashboards, forms, and product UI. These examples use Base UI primitives from @base-ui/react and stay fully compatible with Shadcn Create so radius, color, and typography match your configured theme.
Browse all 4 Shadcn Date Selector components for copy-ready layouts, dashboards, and forms built with Tailwind CSS in the ReUI library.
import {
DateSelector,
type DateSelectorValue,
} from "@/components/reui/date-selector"const [value, setValue] = useState<DateSelectorValue | undefined>()
return <DateSelector value={value} onChange={setValue} label="Due date" />The main component for selecting dates and time periods.
The structure of the value returned by the onChange callback.
"use client"
import { useState } from "react"
import {
DateSelector,
type DateSelectorValue,
} from "@/components/reui/date-selector"
import { format } from "date-fns"
import { Card, CardContent } from "@/components/ui/card"
export function Pattern() {
const [value, setValue] = useState<DateSelectorValue | undefined>()
return (
<div className="flex w-full flex-col items-center gap-5">
<Card className="p-0">
<CardContent className="p-3">
<DateSelector
value={value}
onChange={setValue}
label="Due date"
inputHint="Try: 2025, Q4, 05/10/2025"
/>
</CardContent>
</Card>
{value ? (
<pre className="bg-muted w-full overflow-auto rounded-md p-3 font-mono text-xs md:w-[500px]">
{JSON.stringify(
value,
(key, val) => {
if (val instanceof Date) {
return format(val, "MM/dd/yyyy")
}
return val
},
2
)}
</pre>
) : (
<div className="text-muted-foreground text-sm">
No value selected. Select a date to see the debug information.
</div>
)}
</div>
)
}
"use client"
import { useEffect, useState } from "react"
import {
DateSelector,
formatDateValue,
type DateSelectorValue,
} from "@/components/reui/date-selector"
import { Button } from "@/components/ui/button"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { Separator } from "@/components/ui/separator"
import { CalendarIcon } from 'lucide-react'
export function Pattern() {
const [value, setValue] = useState<DateSelectorValue | undefined>()
const [open, setOpen] = useState(false)
const [internalValue, setInternalValue] = useState<
DateSelectorValue | undefined
>(value)
const formattedValue = value ? formatDateValue(value) : ""
const displayText = formattedValue || "Select a date"
useEffect(() => {
if (open) {
setInternalValue(value)
}
}, [open, value])
const handleApply = () => {
setValue(internalValue)
setOpen(false)
}
const handleCancel = () => {
setInternalValue(value)
setOpen(false)
}
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger
render={
<Button variant="outline" className="w-56 justify-start">
<CalendarIcon />
{displayText}
</Button>
}
/>
<PopoverContent className="w-auto gap-3 p-0" align="start" sideOffset={4}>
<div className="p-3">
<DateSelector
value={internalValue}
onChange={setInternalValue}
allowRange={true}
label="Due date"
inputHint="Try: 2025, Q4, 05/10/2025"
/>
</div>
<Separator className="p-0" />
<div className="flex justify-end gap-2 p-3 pt-0">
<Button variant="outline" onClick={handleCancel}>
Cancel
</Button>
<Button onClick={handleApply}>Apply</Button>
</div>
</PopoverContent>
</Popover>
)
}
"use client"
import { useEffect, useState } from "react"
import {
DateSelector,
formatDateValue,
type DateSelectorValue,
} from "@/components/reui/date-selector"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { CalendarIcon } from 'lucide-react'
export function Pattern() {
const [value, setValue] = useState<DateSelectorValue | undefined>()
const [open, setOpen] = useState(false)
const [internalValue, setInternalValue] = useState<
DateSelectorValue | undefined
>(value)
const formattedValue = value ? formatDateValue(value) : ""
const displayText = formattedValue || "Select a date"
useEffect(() => {
if (open) {
setInternalValue(value)
}
}, [open, value])
const handleApply = () => {
if (internalValue) {
setValue(internalValue)
}
setOpen(false)
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger
render={
<Button variant="outline" className="w-56 justify-start">
<CalendarIcon />
{displayText}
</Button>
}
/>
<DialogContent className="sm:max-w-lg" showCloseButton={false}>
<DialogHeader>
<DialogTitle>Select Due Date</DialogTitle>
</DialogHeader>
<DateSelector
value={internalValue}
onChange={setInternalValue}
showInput={true}
/>
<DialogFooter>
<DialogClose render={<Button variant="outline">Cancel</Button>} />
<Button onClick={handleApply}>Apply</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
"use client"
import { useEffect, useMemo, useState } from "react"
import {
DateSelector,
DEFAULT_DATE_SELECTOR_I18N,
formatDateValue,
type DateSelectorI18nConfig,
type DateSelectorValue,
} from "@/components/reui/date-selector"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { CalendarIcon, ChevronDownIcon } from 'lucide-react'
// Helper function to create i18n config from translations
function createI18nConfig(
translations: Partial<DateSelectorI18nConfig>
): DateSelectorI18nConfig {
return { ...DEFAULT_DATE_SELECTOR_I18N, ...translations }
}
// Language-specific translations
const translations: Record<string, Partial<DateSelectorI18nConfig>> = {
es: {
selectDate: "Seleccionar fecha",
apply: "Aplicar",
cancel: "Cancelar",
clear: "Limpiar",
today: "Hoy",
filterTypes: {
is: "es",
before: "antes de",
after: "después de",
between: "entre",
},
periodTypes: {
day: "DÃa",
month: "Mes",
quarter: "Trimestre",
halfYear: "Semestre",
year: "Año",
},
months: [
"Enero",
"Febrero",
"Marzo",
"Abril",
"Mayo",
"Junio",
"Julio",
"Agosto",
"Septiembre",
"Octubre",
"Noviembre",
"Diciembre",
],
monthsShort: [
"Ene",
"Feb",
"Mar",
"Abr",
"May",
"Jun",
"Jul",
"Ago",
"Sep",
"Oct",
"Nov",
"Dic",
],
quarters: ["T1", "T2", "T3", "T4"],
halfYears: ["S1", "S2"],
weekdays: [
"Domingo",
"Lunes",
"Martes",
"Miércoles",
"Jueves",
"Viernes",
"Sábado",
],
weekdaysShort: ["Do", "Lu", "Ma", "Mi", "Ju", "Vi", "Sá"],
placeholder: "Seleccionar fecha...",
rangePlaceholder: "Seleccionar rango de fechas...",
},
fr: {
selectDate: "Sélectionner une date",
apply: "Appliquer",
cancel: "Annuler",
clear: "Effacer",
today: "Aujourd'hui",
filterTypes: {
is: "est",
before: "avant",
after: "après",
between: "entre",
},
periodTypes: {
day: "Jour",
month: "Mois",
quarter: "Trimestre",
halfYear: "Semestre",
year: "Année",
},
months: [
"Janvier",
"Février",
"Mars",
"Avril",
"Mai",
"Juin",
"Juillet",
"Août",
"Septembre",
"Octobre",
"Novembre",
"Décembre",
],
monthsShort: [
"Jan",
"Fév",
"Mar",
"Avr",
"Mai",
"Juin",
"Juil",
"Aoû",
"Sep",
"Oct",
"Nov",
"Déc",
],
quarters: ["T1", "T2", "T3", "T4"],
halfYears: ["S1", "S2"],
weekdays: [
"Dimanche",
"Lundi",
"Mardi",
"Mercredi",
"Jeudi",
"Vendredi",
"Samedi",
],
weekdaysShort: ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"],
placeholder: "Sélectionner une date...",
rangePlaceholder: "Sélectionner une plage de dates...",
},
de: {
selectDate: "Datum auswählen",
apply: "Anwenden",
cancel: "Abbrechen",
clear: "Löschen",
today: "Heute",
filterTypes: {
is: "ist",
before: "vor",
after: "nach",
between: "zwischen",
},
periodTypes: {
day: "Tag",
month: "Monat",
quarter: "Quartal",
halfYear: "Halbjahr",
year: "Jahr",
},
months: [
"Januar",
"Februar",
"März",
"April",
"Mai",
"Juni",
"Juli",
"August",
"September",
"Oktober",
"November",
"Dezember",
],
monthsShort: [
"Jan",
"Feb",
"Mär",
"Apr",
"Mai",
"Jun",
"Jul",
"Aug",
"Sep",
"Okt",
"Nov",
"Dez",
],
quarters: ["Q1", "Q2", "Q3", "Q4"],
halfYears: ["H1", "H2"],
weekdays: [
"Sonntag",
"Montag",
"Dienstag",
"Mittwoch",
"Donnerstag",
"Freitag",
"Samstag",
],
weekdaysShort: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
placeholder: "Datum auswählen...",
rangePlaceholder: "Datumsbereich auswählen...",
},
}
// Internationalization configurations
const i18nConfigs: Record<string, DateSelectorI18nConfig> = {
en: DEFAULT_DATE_SELECTOR_I18N,
es: createI18nConfig(translations.es),
fr: createI18nConfig(translations.fr),
de: createI18nConfig(translations.de),
}
// Language metadata
const languageMetadata = {
en: {
label: "English",
flag: "🇺🇸",
dateFormat: "MM/dd/yyyy",
weekStartsOn: 0 as const,
ui: {
label: "Due date",
hint: "Try: 2025, Q4, 05/10/2025",
placeholder: "Select a date",
},
},
es: {
label: "Español",
flag: "🇪🇸",
dateFormat: "dd/MM/yyyy",
weekStartsOn: 1 as const,
ui: {
label: "Fecha de vencimiento",
hint: "Prueba: 2025, T4, 05/10/2025",
placeholder: "Seleccionar una fecha",
},
},
fr: {
label: "Français",
flag: "🇫🇷",
dateFormat: "dd/MM/yyyy",
weekStartsOn: 1 as const,
ui: {
label: "Date d'échéance",
hint: "Essayez: 2025, T4, 05/10/2025",
placeholder: "Sélectionner une date",
},
},
de: {
label: "Deutsch",
flag: "🇩🇪",
dateFormat: "dd.MM.yyyy",
weekStartsOn: 1 as const,
ui: {
label: "Fälligkeitsdatum",
hint: "Versuchen Sie: 2025, Q4, 05.10.2025",
placeholder: "Datum auswählen",
},
},
}
// Language options for the selector
const languageOptions = Object.entries(languageMetadata).map(
([value, meta]) => ({
value,
label: meta.label,
flag: meta.flag,
})
)
export function Pattern() {
const [value, setValue] = useState<DateSelectorValue | undefined>()
const [open, setOpen] = useState(false)
const [internalValue, setInternalValue] = useState<
DateSelectorValue | undefined
>(value)
const [currentLanguage, setCurrentLanguage] =
useState<keyof typeof languageMetadata>("fr")
// Get current language metadata and i18n config
const currentMeta = useMemo(
() => languageMetadata[currentLanguage] || languageMetadata.en,
[currentLanguage]
)
const currentI18n = useMemo(
() => i18nConfigs[currentLanguage] || i18nConfigs.en,
[currentLanguage]
)
useEffect(() => {
if (open) {
setInternalValue(value)
}
}, [open, value])
const formattedValue = useMemo(
() =>
value ? formatDateValue(value, currentI18n, currentMeta.dateFormat) : "",
[value, currentI18n, currentMeta.dateFormat]
)
const displayText = formattedValue || currentMeta.ui.placeholder
const handleApply = () => {
setValue(internalValue)
setOpen(false)
}
const handleCancel = () => {
setInternalValue(value)
setOpen(false)
}
return (
<div className="flex h-full w-full grow flex-col items-stretch gap-4">
<div className="flex w-full justify-end">
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
variant="outline"
size="sm"
className="flex items-center gap-2"
>
<span>{currentMeta.flag}</span>
<span>{currentMeta.label}</span>
<ChevronDownIcon />
</Button>
}
/>
<DropdownMenuContent align="start">
{languageOptions.map((lang) => (
<DropdownMenuItem
key={lang.value}
onClick={() =>
setCurrentLanguage(
lang.value as keyof typeof languageMetadata
)
}
className="flex items-center gap-2"
>
<span>{lang.flag}</span>
<span>{lang.label}</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex grow items-center justify-center">
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger
render={
<Button variant="outline" className="w-56 justify-start">
<CalendarIcon />
{displayText}
</Button>
}
/>
<DialogContent className="sm:max-w-lg" showCloseButton={false}>
<DialogHeader>
<DialogTitle>{currentMeta.ui.label}</DialogTitle>
</DialogHeader>
<DateSelector
value={internalValue}
onChange={setInternalValue}
showInput={true}
i18n={currentI18n}
dayDateFormat={currentMeta.dateFormat}
weekStartsOn={currentMeta.weekStartsOn}
/>
<DialogFooter>
<DialogClose
render={
<Button variant="outline" onClick={handleCancel}>
{currentI18n.cancel}
</Button>
}
/>
<Button onClick={handleApply}>{currentI18n.apply}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>
)
}