MUI Components
NPMGithub

Styled form with reusable components

A form built from reusable Styled* wrappers — TextField, Select, Autocomplete, a customized DatePicker and an iOS-style Switch — controlled with plain React state, each with required 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 MUITextField, which can be used throughout the application.
 *
 * In this example, the component accepts all the props of MUITextField 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:
 * - MUINumberInput
 * - MUITagsInput
 * - MUIPasswordInput
 */

import { Fragment, type ReactNode } from 'react';
import Typography from '@mui/material/Typography';
import PriorityHighIcon from '@mui/icons-material/PriorityHigh';
import MUITextField, { type MUITextFieldProps } from '@nish1896/mui-components/mui/textfield';

type StyledTextFieldProps = Omit<
  MUITextFieldProps,
  'renderError' | 'showLabelAboveFormField' | 'variant'
>;

type StyledErrorMsgProps = {
  errorMessage: ReactNode;
};

const StyledErrorMsg = ({ errorMessage }: StyledErrorMsgProps) => {
  return (
    <Fragment>
      {Boolean(errorMessage) && (
        <Typography
          variant="body2"
          sx={{
            alignItems: 'center',
            display: 'flex',
            gap: 0.5
          }}
        >
          <PriorityHighIcon color="error" fontSize="small" />
          {errorMessage}
        </Typography>
      )}
    </Fragment>
  );
};

const StyledTextField = (
  props: StyledTextFieldProps
) => {
  const { formHelperTextProps, ...rest } = props;
  const {
    sx: helperTextSx,
    ...otherFormHelperTextProps
  } = formHelperTextProps ?? {};
  const helperTextSxList = Array.isArray(helperTextSx)
    ? helperTextSx
    : [];

  if (helperTextSx && !Array.isArray(helperTextSx)) {
    helperTextSxList.push(helperTextSx);
  }

  return (
    <MUITextField
      {...rest}
      variant="standard"
      showLabelAboveFormField
      formHelperTextProps={{
        ...otherFormHelperTextProps,
        sx: [
          ...helperTextSxList,
          { ml: 0 }
        ]
      }}
      renderError={error => (
        <StyledErrorMsg errorMessage={error} />
      )}
    />
  );
};

export default StyledTextField;

/**
 * The below code snippet illustrates how to create a reusable styled Select
 * component using MUISelect, which can be used throughout the application.
 *
 * A similar approach can be taken to create reusable styled components for:
 * - MUINativeSelect
 * - MUICheckboxGroup
 * - MUIRadioGroup
 *
 * The only difference being that for all the above components, "multiple" generic
 * prop would not be included in the type definition of the styled component.
 */

import { Poppins } from 'next/font/google';
import MUISelect, {
  type MUISelectProps
} from '@nish1896/mui-components/mui/select';
import type { StrNumObjOption } from '@nish1896/mui-components/types';

const poppins = Poppins({
  subsets: ['latin'],
  style: 'italic',
  weight: '500'
});

type StyledSelectProps<
  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 = false
> = Omit<
  MUISelectProps<Option, LabelKey, ValueKey, Multiple>,
  'showLabelAboveFormField'
>;

const StyledSelect = <
  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 = false
>({
  ...rest
}: StyledSelectProps<Option, LabelKey, ValueKey, Multiple>) => {
  return (
    <MUISelect
      showLabelAboveFormField
      formLabelProps={{
        sx: {
          fontFamily: poppins.style.fontFamily,
          fontWeight: 600
        }
      }}
      {...rest}
    />
  );
};

export default StyledSelect;

/**
 * The below code snippet illustrates how to create a reusable styled Autocomplete
 * component using MUIAutocomplete, which can be used throughout the application.
 *
 * A similar approach can be taken to create reusable styled components for:
 * - MUIAutocompleteObject
 * - MUIMultiAutocomplete
 * - MUIMultiAutocompleteObject
 */

import MUIAutocomplete, {
  type MUIAutocompleteProps
} from '@nish1896/mui-components/mui/autocomplete';
import type { StrObjOption } from '@nish1896/mui-components/types';

type StyledAutocompleteProps<
  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<MUIAutocompleteProps<Option, LabelKey, ValueKey, true, DisableClearable, FreeSolo>, 'multiple'>;

const StyledAutocomplete = <
  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<Option, LabelKey, ValueKey, DisableClearable, FreeSolo>) => {
  return (
    <MUIAutocomplete
      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://v7.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 { Theme } from '@mui/material/styles';
import MUISwitch, {
  type MUISwitchProps
} from '@nish1896/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 = (sx: MUISwitchProps['sx']) =>
  /* eslint-disable-next-line no-nested-ternary */
  (Array.isArray(sx) ? sx : sx ? [sx] : []);

const StyledIOSSwitch = ({
  sx,
  formControlLabelProps,
  ...rest
}: MUISwitchProps) => {
  const { sx: labelSx, ...otherLabelProps } = formControlLabelProps ?? {};
  return (
    <MUISwitch
      disableRipple
      focusVisibleClassName=".Mui-focusVisible"
      {...rest}
      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 MUIDatePicker, 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 (`value`, `onValueChange`, `errorMessage`…).
 * MUIDatePicker 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:
 * - MUITimePicker
 * - MUIDateTimePicker
 */

import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
import type { PickerValidDate } from '@mui/x-date-pickers/models';
import {
  MUIDatePicker,
  type MUIDatePickerProps
} from '@nish1896/mui-components/mui-pickers/date';

const brandColor = '#007bff';

type StyledDatePickerProps<TDate extends PickerValidDate = PickerValidDate>
  = Omit<MUIDatePickerProps<TDate>, 'showLabelAboveFormField'>;

const StyledDatePicker = <TDate extends PickerValidDate = PickerValidDate>({
  slotProps,
  ...rest
}: StyledDatePickerProps<TDate>) => {
  return (
    <MUIDatePicker
      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;