--- name: date-pickers description: MUI X Date/Time Pickers setup, configuration, and form integration triggers: - DatePicker - date picker - TimePicker - DateTimePicker - calendar - date-pickers allowed-tools: - Read - Glob - Grep - Write - Edit globs: - "*.tsx" - "*.jsx" --- # MUI X Date Pickers ## Package Installation ```bash # Core package (free) npm install @mui/x-date-pickers # Pro package (requires license — DateRangePicker, DateTimeRangePicker, etc.) npm install @mui/x-date-pickers-pro # Choose ONE date adapter: npm install dayjs # recommended — smallest, fastest npm install date-fns # most popular in React ecosystem npm install luxon # feature-rich, immutable npm install moment # legacy; avoid for new projects ``` ## Date Adapters ### dayjs (recommended) dayjs is the recommended adapter: smallest bundle (~7 KB), fastest parse, covers all picker features. ```tsx import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; // Wrap your app root (or page root) — every picker must be a descendant export default function App() { return ( ); } ``` ### date-fns ```tsx import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import { enUS } from 'date-fns/locale'; // import locale from date-fns/locale, NOT date-fns ``` ### luxon ```tsx import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon'; ``` ## Core Picker Components ### DatePicker ```tsx import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import dayjs, { Dayjs } from 'dayjs'; import { useState } from 'react'; function BasicDatePicker() { const [value, setValue] = useState(dayjs('2024-01-15')); return ( setValue(newValue)} /> ); } ``` ### TimePicker ```tsx import { TimePicker } from '@mui/x-date-pickers/TimePicker'; function BasicTimePicker() { const [value, setValue] = useState(dayjs().hour(10).minute(30)); return ( setValue(newValue)} ampm={false} // 24-hour format views={['hours', 'minutes']} // omit 'seconds' if not needed /> ); } ``` ### DateTimePicker ```tsx import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; function BasicDateTimePicker() { const [value, setValue] = useState(null); return ( setValue(newValue)} format="DD/MM/YYYY HH:mm" ampm={false} /> ); } ``` ### DateRangePicker (Pro — requires license) ```tsx import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; import { DateRange } from '@mui/x-date-pickers-pro'; import { LicenseInfo } from '@mui/x-license'; // Set license key once at app entry point LicenseInfo.setLicenseKey('your-license-key'); function BookingPicker() { const [value, setValue] = useState>([null, null]); return ( setValue(newValue)} localeText={{ start: 'Check-in', end: 'Check-out' }} /> ); } ``` ### DateTimeRangePicker (Pro) ```tsx import { DateTimeRangePicker } from '@mui/x-date-pickers-pro/DateTimeRangePicker'; function EventTimePicker() { const [value, setValue] = useState>([null, null]); return ( ); } ``` ## Slots and slotProps (v6 API) `slots` replaces `components`; `slotProps` replaces `componentsProps`. Always use the new API. ### textField slot ```tsx import TextField from '@mui/material/TextField'; ``` ### actionBar slot ```tsx // Available actions: 'clear' | 'today' | 'cancel' | 'accept' ``` ### toolbar slot ```tsx import { DatePickerToolbar } from '@mui/x-date-pickers/DatePicker'; ( ), }} /> ``` ### day slot (custom day rendering) ```tsx import { PickersDay, PickersDayProps } from '@mui/x-date-pickers/PickersDay'; import Badge from '@mui/material/Badge'; interface ServerDayProps extends PickersDayProps { highlightedDays?: number[]; } function ServerDay({ highlightedDays = [], day, outsideCurrentMonth, ...other }: ServerDayProps) { const isHighlighted = !outsideCurrentMonth && highlightedDays.includes(day.date()); return ( ); } // Usage ``` ## Validation ### minDate / maxDate ```tsx // Restrict to a specific year range ``` ### shouldDisableDate ```tsx // Disable weekends day.day() === 0 || day.day() === 6} value={value} onChange={setValue} /> // Disable a list of holiday dates const holidays = [dayjs('2024-12-25'), dayjs('2024-01-01'), dayjs('2024-07-04')]; holidays.some((h) => h.isSame(day, 'day'))} value={value} onChange={setValue} /> // Combined: no past dates, no weekends { const isWeekend = day.day() === 0 || day.day() === 6; const isPast = day.isBefore(dayjs(), 'day'); return isWeekend || isPast; }} value={value} onChange={setValue} /> ``` ### shouldDisableTime ```tsx // Business hours only: 8am–6pm { if (view === 'hours') return value < 8 || value > 18; return false; }} value={value} onChange={setValue} /> // Exclude lunch 12–13 and only 15-minute intervals { if (view === 'hours') return value === 12 || value === 13; if (view === 'minutes') return value % 15 !== 0; return false; }} value={value} onChange={setValue} /> ``` ### onError callback ```tsx const [errorMsg, setErrorMsg] = useState(null); { const messages: Record = { minDate: 'Date must be on or after January 1, 2020', maxDate: 'Date must be before 2031', invalidDate: 'Please enter a valid date', disablePast: 'Past dates are not allowed', shouldDisableDate: 'This date is unavailable', }; setErrorMsg(reason ? (messages[reason] ?? 'Invalid date') : null); }} slotProps={{ textField: { error: !!errorMsg, helperText: errorMsg, }, }} /> ``` ## Form Integration with React Hook Form ### Basic controlled DatePicker ```tsx import { Controller, useForm } from 'react-hook-form'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import dayjs, { Dayjs } from 'dayjs'; interface FormValues { birthDate: Dayjs | null; } function DateForm() { const { control, handleSubmit, } = useForm({ defaultValues: { birthDate: null }, }); const onSubmit = (data: FormValues) => { console.log(data.birthDate?.toISOString()); // ISO string for APIs }; return (
(v?.isValid() ? true : 'Please enter a valid date'), }} render={({ field, fieldState }) => ( )} /> ); } ``` ### With Zod + React Hook Form ```tsx import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; const schema = z.object({ startDate: z .custom((v) => dayjs.isDayjs(v) && v.isValid(), 'Invalid date') .refine((v) => v.isAfter(dayjs()), 'Must be a future date'), endDate: z .custom((v) => dayjs.isDayjs(v) && v.isValid(), 'Invalid date'), }).refine((d) => d.endDate.isAfter(d.startDate), { message: 'End must be after start', path: ['endDate'], }); function ZodDateForm() { const { control, handleSubmit } = useForm({ resolver: zodResolver(schema), defaultValues: { startDate: null, endDate: null }, }); return (
{['startDate', 'endDate'].map((name) => ( ( )} /> ))} ); } ``` ### DateRangePicker with React Hook Form ```tsx import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; import { DateRange } from '@mui/x-date-pickers-pro'; interface FormValues { period: DateRange; } function RangeForm() { const { control, handleSubmit } = useForm({ defaultValues: { period: [null, null] }, }); return (
{ if (!start || !end) return 'Both dates are required'; if (!start.isValid() || !end.isValid()) return 'Invalid date'; if (end.isBefore(start)) return 'End must be after start'; return true; }, }} render={({ field, fieldState }) => ( <> {fieldState.error && (

{fieldState.error.message}

)} )} /> ); } ``` ## Static, Mobile, and Desktop Variants ### StaticDatePicker (always visible) ```tsx import { StaticDatePicker } from '@mui/x-date-pickers/StaticDatePicker'; function InlineCalendar() { const [value, setValue] = useState(dayjs()); return ( ); } ``` ### MobileDatePicker (dialog — forced) ```tsx import { MobileDatePicker } from '@mui/x-date-pickers/MobileDatePicker'; // Always uses full-screen dialog, even on desktop ``` ### DesktopDatePicker (popover — forced) ```tsx import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker'; // Always uses popover, even on mobile ``` ### DatePicker (responsive — default, recommended) ```tsx import { DatePicker } from '@mui/x-date-pickers/DatePicker'; // Automatically uses dialog on touch, popover on pointer devices ``` ## Localization ```tsx import 'dayjs/locale/de'; // German import 'dayjs/locale/fr'; // French import 'dayjs/locale/ja'; // Japanese // Override specific button/label text ``` ## Custom Input Format ```tsx // dayjs format tokens: https://day.js.org/docs/en/display/format // January 15, 2024 ``` ## Controlled Open State ```tsx const [open, setOpen] = useState(false); setOpen(true)} onClose={() => setOpen(false)} value={value} onChange={setValue} slotProps={{ textField: { onClick: () => setOpen(true), InputProps: { readOnly: true }, // prevent keyboard entry }, openPickerButton: { style: { display: 'none' } }, // hide redundant icon }} /> ``` ## onChange vs onAccept ```tsx // onChange fires on every keystroke in the text field (value may still be invalid) // onAccept fires only when the user confirms (clicks day in calendar or presses OK) { setValue(newValue); // keep field responsive }} onAccept={(acceptedValue) => { // safe to call API or trigger side effects here void fetchAvailability(acceptedValue?.toISOString()); }} /> ``` ## Common Pitfalls - Always wrap pickers in `` — omitting it throws at runtime. - Use `null` (not `undefined`) as the empty value; undefined causes uncontrolled/controlled warnings. - The `value` prop type must match the adapter: `Dayjs` for AdapterDayjs, `Date` for AdapterDateFns. - For `date-fns`, import locale from `date-fns/locale`, not from `date-fns` root. - Use deep imports (`@mui/x-date-pickers/DatePicker`) not barrel imports for tree-shaking. - For SSR/Next.js, wrap picker in `` or use `dynamic(() => ..., { ssr: false })` to prevent hydration mismatch. - Pro components require a valid license key set via `LicenseInfo.setLicenseKey(...)` before first render. - `shouldDisableDate` returning `true` for all days causes an infinite render loop — always leave some dates enabled. --- ## Advanced Patterns ### Timezone Handling ```tsx import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; dayjs.extend(utc); dayjs.extend(timezone); // Display in user's timezone, store as UTC { const utcValue = newValue?.utc().toISOString(); saveToServer(utcValue); }} /> // System timezone (auto-detect) // UTC ``` ### Date Range Shortcuts (Pro) ```tsx import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; const shortcuts = [ { label: 'Today', getValue: () => { const t = dayjs(); return [t, t]; } }, { label: 'This Week', getValue: () => [dayjs().startOf('week'), dayjs().endOf('week')] }, { label: 'Last 7 Days', getValue: () => [dayjs().subtract(7, 'day'), dayjs()] }, { label: 'Last 30 Days', getValue: () => [dayjs().subtract(30, 'day'), dayjs()] }, { label: 'This Month', getValue: () => [dayjs().startOf('month'), dayjs().endOf('month')] }, { label: 'This Year', getValue: () => [dayjs().startOf('year'), dayjs().endOf('year')] }, ]; ``` ### Custom Day Rendering ```tsx import { PickersDay, PickersDayProps } from '@mui/x-date-pickers/PickersDay'; import Badge from '@mui/material/Badge'; function HighlightedDay(props: PickersDayProps & { highlightedDays?: number[] }) { const { highlightedDays = [], day, outsideCurrentMonth, ...other } = props; const isHighlighted = !outsideCurrentMonth && highlightedDays.includes(day.date()); return ( ); } ``` ### Custom Field Component Replace the default TextField with a completely custom input: ```tsx import { useDateField } from '@mui/x-date-pickers/DateField'; function CustomDateInput(props: any) { const { inputRef, inputProps, ...fieldProps } = useDateField(props); return ( ); } ``` ### Digital Clock for Time Picker ```tsx import { DigitalClock } from '@mui/x-date-pickers/DigitalClock'; import { MultiSectionDigitalClock } from '@mui/x-date-pickers/MultiSectionDigitalClock'; // Single-section (scrollable list of times) // Multi-section (hours, minutes, AM/PM in columns) ``` ### Business Hours Validation ```tsx { if (view === 'hours') { return value.hour() < 9 || value.hour() > 17; } if (view === 'minutes') { // Only allow 15-min intervals return value.minute() % 15 !== 0; } return false; }} minTime={dayjs().set('hour', 9).set('minute', 0)} maxTime={dayjs().set('hour', 17).set('minute', 0)} /> ``` ### Action Bar Customization ```tsx ```