useMuiXTimePickerAdapter
Returns adapter props for building custom MUI X TimePicker integrations with React Hook Form, including minTime, maxTime, minutesStep and shouldDisableTime validation.
useMuiXTimePickerAdapter connects a field to React Hook Form and returns props you spread onto MUI X's TimePicker. It's what MuiXTimePickerElement uses internally.
Use the element for most forms. Reach for the hook when you want your own time field component, for example one that always enforces opening hours or a minute step, or one that shares form wiring across several pickers. This page describes the time validation that all time hooks share.
Import
import { useMuiXTimePickerAdapter } from "@piplup/rhf-adapters/mui-x-date-pickers";Usage
This ShiftStartField only accepts times between 08:00 and 18:00 on the quarter hour, with its own messages. Submit it empty, or type 07:30 or 09:10, to see the validation.
"use client";import dayjs, { type Dayjs } from "dayjs";import Stack from "@mui/material/Stack";import { LocalizationProvider, TimePicker } from "@mui/x-date-pickers";import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";import { MuiButtonElement } from "@piplup/rhf-adapters/mui-material";import { useMuiXTimePickerAdapter } from "@piplup/rhf-adapters/mui-x-date-pickers";import { type Control, useForm } from "react-hook-form";type FormValues = { shiftStart: Dayjs | null };// Only the time of day is compared, so the date part of these values does not matter.const opensAt = dayjs().hour(8).minute(0);const closesAt = dayjs().hour(18).minute(0);// Shifts start between 08:00 and 18:00, on the quarter hour.function ShiftStartField({ control }: { control: Control<FormValues> }) { const adapter = useMuiXTimePickerAdapter<Dayjs, FormValues, "shiftStart", HTMLDivElement>({ control, name: "shiftStart", required: true, composeHelperText: true, minTime: opensAt, maxTime: closesAt, minutesStep: 15, messages: { required: "Pick a start time", minTime: "We open at 08:00", maxTime: "The last shift starts at 18:00", minutesStep: "Use a quarter hour (:00, :15, :30 or :45)", }, slotProps: { textField: { helperText: "08:00 to 18:00, every 15 minutes" }, }, }); return <TimePicker label="Shift start" ampm={false} {...adapter} />;}export default function Page() { const { control, handleSubmit } = useForm<FormValues>({ defaultValues: { shiftStart: null }, }); return ( <LocalizationProvider dateAdapter={AdapterDayjs}> <form noValidate onSubmit={handleSubmit((values) => alert(values.shiftStart?.format("HH:mm")))} > <Stack direction="column" spacing={2}> <ShiftStartField control={control} /> <div> <MuiButtonElement control={control} variant="contained" type="submit"> Save shift </MuiButtonElement> </div> </Stack> </form> </LocalizationProvider> );}The hook must run inside a LocalizationProvider, because it reads the date adapter to validate values.
How it works
Value. The field value is passed to the picker as-is, with undefined converted to null so the picker stays controlled. Pass transform if your form stores something other than a date object.
Validation props reach the picker too. minTime, maxTime, minutesStep, shouldDisableTime, disablePast, disableFuture and disableIgnoringDatePartForTimeValidation are returned by the hook, so the picker greys out the same times that validation rejects. disablePast and disableFuture default to false.
Validation. The hook runs MUI X's validateTime on every non-empty value. Empty values pass; add required to reject them. Checks run in this order and only the first failure is reported:
| Order | Key | Fails when | Default message |
|---|---|---|---|
| 1 | invalidDate | the value is not a valid date, e.g. a half-typed time | Date is not a valid date |
| 2 | minTime | the time is before minTime | Time is earlier than the minimum allowed time |
| 3 | maxTime | the time is after maxTime | Time is later than the maximum allowed time |
| 4 | disableFuture | the value is after now | Future date is not allowed |
| 5 | disablePast | the value is before now | Past date is not allowed |
| 6 | shouldDisableTime-hours | shouldDisableTime(value, "hours") returns true | Hour is disabled |
| 7 | shouldDisableTime-minutes | shouldDisableTime(value, "minutes") returns true | Minute is disabled |
| 8 | shouldDisableTime-seconds | shouldDisableTime(value, "seconds") returns true | Second is disabled |
| 9 | minutesStep | the minutes are not a multiple of minutesStep | Minute step is not valid |
required uses "Please fill out this field." The messages above are the exact default text.
Time of day only. minTime and maxTime compare only the time of day, so the date part of the values you pass is ignored. Set disableIgnoringDatePartForTimeValidation to compare full date-times instead. disablePast and disableFuture always compare against the current date and time.
Timezone. Validation uses the timezone prop when given, otherwise the timezone of the value.
Custom messages. Pass any key from the table in messages, as a string or a function. A function receives the value, and for minTime, maxTime and minutesStep also the limit, for example (value, minTime) => "Open from " + minTime.format("HH:mm"). invalidDate is typed as a function only, so write it as invalidDate: () => "Enter a valid time".
Errors and helper text. The returned slotProps.textField sets error and helperText from the field state and merges your own slotProps.textField (an object or a function). The hook has no top-level helperText prop; put your hint in slotProps.textField.helperText. With composeHelperText: true an error message replaces that hint while the field is invalid. Without it, only the error state is applied.
Blur and touched. The returned slotProps.textField calls React Hook Form's onBlur, so typing a time and tabbing away marks the field as touched. The returned onClose also calls onBlur and then your own onClose, so closing the picker counts as a blur too.
Your own rules.validate. A validate function or object in rules runs next to the built-in time validation, not instead of it.
Refs. React Hook Form's ref, merged with your inputRef, is returned as inputRef, so setFocus and focus-on-error move focus to the input. The hook's second argument becomes ref on the picker root.
Variants
The other time hooks use the same validation and messages. They differ in where errors can render and how a field becomes touched:
| Hook | Text field | Errors shown by | Touched on |
|---|---|---|---|
useMuiXDesktopTimePickerAdapter | yes | slotProps.textField | input blur or picker close |
useMuiXMobileTimePickerAdapter | yes | slotProps.textField | input blur or dialog close |
useMuiXStaticTimePickerAdapter | no | you render them | never |
useMuiXTimeFieldAdapter | yes, no popup | error / helperText props | input blur |
useMuiXTimeClockAdapter | no | you render them | blur inside the clock |
useMuiXDigitalClockAdapter | no | you render them | blur inside the clock |
useMuiXMultiSectionDigitalClockAdapter | no | you render them | blur inside the clock |
Props
Prop
Type
Return value
Prop
Type
useMuiXTimeFieldAdapter
Returns adapter props for building custom MUI X TimeField integrations with React Hook Form, for keyboard-only time inputs with blur validation.
useMuiXYearCalendarAdapter
Returns adapter props for MUI X YearCalendar with React Hook Form, including how year selection interacts with day-level date validation.