Styled form with reusable components
A form built from reusable Styled* wrappers — TextField, Select, Autocomplete, a customized DatePicker and an iOS-style Switch — wired to React Hook Form with registerOptions validation, and a ConfigProvider supplying shared label/helper styles and the date adapter.
Component Source Code
Source of each reusable Styled* wrapper used above — expand to view or copy.
/**
* The below code snippet illustrates how to create a reusable styled Textfield
* component using RHFTextField, which can be used throughout the application.
*
* In this example, the component accepts all the props of RHFTextField except
* 'renderError', 'variant' and 'showLabelAboveFormField', which have already
* been configured to maintain consistent styling across the application.
* Additionally, it includes a custom error message component that displays an
* error icon alongside the error message when there is an error.
*
* A similar approach can be taken to create reusable styled components for:
* - RHFNumberInput
* - RHFTagsInput
* - RHFPasswordInput
*/
import { Fragment, type ReactNode } from 'react';
import { type FieldValues } from 'react-hook-form';
import Typography from '@mui/material/Typography';
import RHFTextField, { type RHFTextFieldProps } from '@nish1896/rhf-mui-components/mui/textfield';
import PriorityHighIcon from '@mui/icons-material/PriorityHigh';
type StyledRHFTextFieldProps<T extends FieldValues> = Omit<
RHFTextFieldProps<T>,
'renderError' | 'showLabelAboveFormField' | 'variant'
>;
type StyledErrorMsgProps = {
errorMessage: ReactNode;
};
const StyledErrorMsg = ({ errorMessage }: StyledErrorMsgProps) => {
return (
<Fragment>
{!!errorMessage && (
<Typography
variant="body2"
sx={{
alignItems: 'center',
display: 'flex',
gap: 0.5
}}
>
<PriorityHighIcon color="error" fontSize="small" />
{errorMessage}
</Typography>
)}
</Fragment>
);
};
const StyledRHFTextField = <T extends FieldValues>(
props: StyledRHFTextFieldProps<T>
) => {
const { formHelperTextProps, ...rest } = props;
const {
sx: helperTextSx,
...otherFormHelperTextProps
} = formHelperTextProps ?? {};
const helperTextSxList = Array.isArray(helperTextSx)
? helperTextSx
: [];
if (helperTextSx && !Array.isArray(helperTextSx)) {
helperTextSxList.push(helperTextSx);
}
return (
<RHFTextField
{...rest}
variant="standard"
showLabelAboveFormField
formHelperTextProps={{
...otherFormHelperTextProps,
sx: [
...helperTextSxList,
{ ml: 0 }
]
}}
renderError={error => (
<StyledErrorMsg errorMessage={error?.message} />
)}
/>
);
};
export default StyledRHFTextField;
/**
* The below code snippet illustrates how to create a reusable styled Select
* component using RHFSelect, which can be used throughout the application.
*
* `multiple` is left in the prop surface (not `Omit`'d) so its generic
* `Multiple` type param is inferred per call site from the literal passed —
* same as `RHFSelect` itself. Omitting `multiple` from the props type would
* pin `Multiple` to its default and make passing `multiple={false}` a type
* error.
*
* A similar approach can be taken to create reusable styled components for:
* - RHFNativeSelect
* - RHFCheckboxGroup
* - RHFRadioGroup
*/
import { type FieldValues } from 'react-hook-form';
import { Poppins } from 'next/font/google';
import RHFSelect, {
type RHFSelectProps
} from '@nish1896/rhf-mui-components/mui/select';
import type { StrNumObjOption } from '@nish1896/rhf-mui-components/types';
const poppins = Poppins({
subsets: ['latin'],
style: 'italic',
weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900']
});
type StyledSelectProps<
T extends FieldValues,
Option extends StrNumObjOption = StrNumObjOption,
LabelKey extends Extract<keyof Option, string> = Extract<keyof Option, string>,
ValueKey extends Extract<keyof Option, string> = Extract<keyof Option, string>,
Multiple extends boolean = true
> = Omit<
RHFSelectProps<T, Option, LabelKey, ValueKey, Multiple>,
'showLabelAboveFormField'
>;
const StyledSelect = <
T extends FieldValues,
Option extends StrNumObjOption = StrNumObjOption,
LabelKey extends Extract<keyof Option, string> = Extract<keyof Option, string>,
ValueKey extends Extract<keyof Option, string> = Extract<keyof Option, string>,
Multiple extends boolean = true
>({
formLabelProps,
...rest
}: StyledSelectProps<T, Option, LabelKey, ValueKey, Multiple>) => {
return (
<RHFSelect
formLabelProps={{
...formLabelProps,
sx: {
fontFamily: poppins.style.fontFamily,
fontWeight: 400,
...formLabelProps?.sx
}
}}
showLabelAboveFormField
{...rest}
/>
);
};
export default StyledSelect;
/**
* The below code snippet illustrates how to create a reusable styled Autocomplete
* component using RHFAutocomplete, which can be used throughout the application.
*
* A similar approach can be taken to create reusable styled components for:
* - RHFAutocompleteObject
* - RHFMultiAutocomplete
* - RHFMultiAutocompleteObject
*/
import { type FieldValues } from 'react-hook-form';
import RHFAutocomplete, {
type RHFAutocompleteProps
} from '@nish1896/rhf-mui-components/mui/autocomplete';
import type { StrObjOption } from '@nish1896/rhf-mui-components/types';
type StyledAutocompleteProps<
T extends FieldValues,
Option extends StrObjOption = StrObjOption,
LabelKey extends Extract<keyof Option, string> = Extract<keyof Option, string>,
ValueKey extends Extract<keyof Option, string> = Extract<keyof Option, string>,
DisableClearable extends boolean = false,
FreeSolo extends boolean = false
> = Omit<RHFAutocompleteProps<T, Option, LabelKey, ValueKey, true, DisableClearable, FreeSolo>, 'multiple'>;
const StyledAutocomplete = <
T extends FieldValues,
Option extends StrObjOption = StrObjOption,
LabelKey extends Extract<keyof Option, string> = Extract<keyof Option, string>,
ValueKey extends Extract<keyof Option, string> = Extract<keyof Option, string>,
DisableClearable extends boolean = false,
FreeSolo extends boolean = false
>({
...rest
}: StyledAutocompleteProps<T, Option, LabelKey, ValueKey, DisableClearable, FreeSolo>) => {
return (
<RHFAutocomplete
formHelperTextProps={{
sx: { fontColor: theme => theme.palette.info.main }
}}
multiple
{...rest}
/>
);
};
export default StyledAutocomplete;
/**
* The below snippet illustrates how to reproduce MUI's "iOS style" Switch
* customization (https://mui.com/material-ui/react-switch/#customization)
* on top of `MUISwitch`, so it can be reused across the application while
* keeping `MUISwitch`'s label / helper-text / error handling.
*
* MUI's own example wraps the raw `Switch` with `styled(Switch)(...)`. We don't
* need `styled()` here: `MUISwitch` forwards `sx`, `disableRipple` and
* `focusVisibleClassName` straight to the underlying MUI `Switch`, so the same
* overrides can be supplied through `sx`. The style object is identical to the
* upstream `IOSSwitch` example (`theme.applyStyles('dark', …)` keeps it
* theme-aware in both light and dark mode).
*
* A caller-provided `sx` is merged after the iOS overrides so per-instance
* tweaks still win.
*/
import { type FieldValues } from 'react-hook-form';
import type { Theme } from '@mui/material/styles';
import RHFSwitch, {
type RHFSwitchProps
} from '@nish1896/rhf-mui-components/mui/switch';
const iosSwitchSx = (theme: Theme) => ({
width: 42,
height: 26,
padding: '0px',
'& .MuiSwitch-switchBase': {
padding: '0px',
margin: '2px',
transitionDuration: '300ms',
'&.Mui-checked': {
transform: 'translateX(16px)',
color: '#fff',
'& + .MuiSwitch-track': {
backgroundColor: '#65C466',
opacity: 1,
border: 0,
...theme.applyStyles('dark', {
backgroundColor: '#2ECA45'
})
},
'&.Mui-disabled + .MuiSwitch-track': {
opacity: 0.5
}
},
'&.Mui-focusVisible .MuiSwitch-thumb': {
color: '#33cf4d',
border: '6px solid #fff'
},
'&.Mui-disabled .MuiSwitch-thumb': {
color: theme.palette.grey[100],
...theme.applyStyles('dark', {
color: theme.palette.grey[600]
})
},
'&.Mui-disabled + .MuiSwitch-track': {
opacity: 0.7,
...theme.applyStyles('dark', {
opacity: 0.3
})
}
},
'& .MuiSwitch-thumb': {
boxSizing: 'border-box',
width: 22,
height: 22
},
'& .MuiSwitch-track': {
borderRadius: `${26 / 2}px`,
backgroundColor: '#E9E9EA',
opacity: 1,
transition: theme.transitions.create(['background-color'], {
duration: 500
}),
...theme.applyStyles('dark', {
backgroundColor: '#39393D'
})
}
});
const toSxArray = <T extends FieldValues>(sx: RHFSwitchProps<T>['sx']) =>
/* eslint-disable-next-line no-nested-ternary */
(Array.isArray(sx) ? sx : sx ? [sx] : []);
const StyledIOSSwitch = <T extends FieldValues>({
sx,
formControlLabelProps,
...rest
}: RHFSwitchProps<T>) => {
const { sx: labelSx, ...otherLabelProps } = formControlLabelProps ?? {};
return (
<RHFSwitch
{...rest}
disableRipple
focusVisibleClassName=".Mui-focusVisible"
formControlLabelProps={{
...otherLabelProps,
/*
* `gap` puts 12px between the switch and its label. `ml: 0` clears
* MUI's default `FormControlLabel` `-11px` left margin (meant to align a
* checkbox/switch ripple) so the switch lines up with the other fields.
* Composed as an `sx` array so array/callback `labelSx` values survive.
*/
sx: [{ gap: '12px', ml: 0 }, ...toSxArray(labelSx)]
}}
sx={[iosSwitchSx, ...toSxArray(sx)]}
/>
);
};
export default StyledIOSSwitch;
/**
* The below snippet illustrates how to create a reusable customized DatePicker
* using RHFDatePicker, which can be used throughout the application.
*
* The look is preset here — label above the field, a `dd LLL yyyy` display
* format, a rounded / tinted input, a branded calendar icon and focus outline —
* so callers only pass data props (`fieldName`, `control`, `onValueChange`…).
* RHFDatePicker forwards every underlying MUI `DatePickerProps` (`slots`,
* `slotProps`, `format`, …), so all customization is just props — no `styled()`.
*
* A similar approach can be taken to create reusable styled components for:
* - RHFTimePicker
* - RHFDateTimePicker
*/
import { type FieldValues } from 'react-hook-form';
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
import {
RHFDatePicker,
type RHFDatePickerProps
} from '@nish1896/rhf-mui-components/mui-pickers/date';
const brandColor = '#007bff';
type StyledDatePickerProps<T extends FieldValues>
= Omit<RHFDatePickerProps<T>, 'showLabelAboveFormField'>;
const StyledDatePicker = <T extends FieldValues>({
slotProps,
...rest
}: StyledDatePickerProps<T>) => {
return (
<RHFDatePicker
showLabelAboveFormField
format="dd LLL yyyy"
slots={{ openPickerIcon: CalendarMonthIcon }}
{...rest}
slotProps={{
...slotProps,
textField: {
sx: {
/**
* MUI X pickers use their own `MuiPickers*` field classes, not the
* plain `MuiOutlinedInput-*` ones a TextField would.
*/
'& .MuiPickersInputBase-root': {
borderRadius: '12px',
bgcolor: theme => theme.palette.action.hover
},
'& .MuiPickersInputBase-root.Mui-focused .MuiPickersOutlinedInput-notchedOutline': {
borderColor: brandColor,
borderWidth: 2
},
'& .MuiInputAdornment-root .MuiSvgIcon-root': {
color: brandColor
}
}
}
}}
/>
);
};
export default StyledDatePicker;

