Every Material-UI Form Component Explained (MUI v5)

Forms are an important part of many web UIs.  Interestingly, Material-UI doesn’t have a form component.  It has seven form subcomponents (depending on what components you include), but simply relies on the native React Form component as a parent wrapper.  Alternatively, it could be argued the the FormControl component sometimes takes the place of the React Form component.

In this article I will discuss each form composite component, compare and contrast similar components, and discuss when to use each.  I will also explore a third party React form option called react-final-form which has integration options with MUI.

There is a Code Sandbox with full React code in the Resources section.

FormControl

The Material-UI FormControl component is the most complex of the form composite components.  It can enforce state (disabled, required, etc), add styling through different variants, and detect changes.

When the state is set at the FormControl level it affects certain children elements.  Take a look at the radio portion of my form example (code below).  The FormControl disabled prop is set to true, and the children automatically received appropriate styling.  Also, the radios are no longer selectable.

<FormControl 
  component="fieldset" 
  variant="filled" 
  disabled
>
  <FormLabel 
    component="legend" 
    htmlFor="residence-type-radio"
  >
    Residence
  </FormLabel>
  <RadioGroup
    aria-label="residence"
    id="residence-type-radio"
    defaultValue="homeowner"
    name="radio-buttons-group"
    onChange={handleRadioChange}
  >
    <FormControlLabel
      value="homeowner"
      control={<Radio />}
      label="Homeowner"
    />
      <FormControlLabel 
        value="renter" 
        control={<Radio />} 
        label="Renter" 
      />
        <FormControlLabel 
          value="nomad" 
          control={<Radio />} 
          label="Nomad" />
  </RadioGroup>
  <FormHelperText>Disabled</FormHelperText>
</FormControl>

In the demo I created, the FormControl variants only have a minor effect.  If you set the variant to filled, notice that the alignment of the FormHelperText changes.  FormControl variants have more noticeable effects on Input elements.

Finally, if you add onChange to a FormControl, it will fire when child component values change.  However, this may be of limited use.  The value of the children components may not be easily visible to the change event that gets passed to FormControl’s onChange handler.  What this means is that child components pass an event with event.target.value populated, but the FormControl’s onChange event.target.value is not populated.

FormGroup

The Material-UI FormGroup component’s primary purpose is to vertically or horizontally lay out its children components.  The only special prop for FormGroup is the row prop which control this layout ability.

The FormGroup supports all props of the native React FormGroup component.

Here are all the best components and props for Material-UI form layout.

FormControl vs FormGroup

Simply put, in MUI the FormControl is for controlling state and other data while the FormGroup is mostly for layout.  The FormControl component will generally be wrapping one or many FormGroup components.  Most of the examples involving FormControl and FormGroup follow this pattern.

It is possible some developers would have a FormGroup wrap one or many FormControls simply for layout purposes.  However, a better layout option would be to wrap the FormControls in a Grid.

In my opinion MUI has blurred the lines between FormControl and FormGroup.  If I google the phrase ‘HTML form group’ it will return information the fieldset element.  If I google ‘React form group’ it returns data about the native React FormGroup.  The problem is that these two underlying constructs behave far more like a MUI FormControl than a MUI FormGroup.

Setting this critique aside, the FormControl and FormGroup functionality in MUI is quite robust.  Here’s a great example I found from one of the MUI team members (it wasn’t linked to in the docs): https://codesandbox.io/s/9ywq085k9w?file=/src/index.js.  You will need lower the version of @date-io/date-fns to 1.3.13 to get the Code Sandbox working.

RadioGroup

The Material-UI RadioGroup renders as a div with class MuiFormGroup-root applied to it, just like a FormGroup.  The difference is that it is given a ‘role’ of ‘radiogroup’.

RadioGroup has all the props of FormGroup, most notably the ‘row’ prop for controlling layout.

The onChange handler of RadioGroup passes two props: an object representing the event and a string representing the selected value of the radio buttons.

Take a look at the onChange handler:

const handleRadioChange = (event, value) => {
  setRadioState(event.target.value);
  console.log(event.target.value);
  console.log(value);
};

Both console.log calls will log the same value.

FormGroup vs RadioGroup

FormGroup and RadioGroup are quite similar.  In fact, a RadioGroup is simply a specialized FormGroup: it accepts all the same props as FormGroup (plus additional props), but it has more semantic meaning in the DOM.

Make sure to use RadioGroup in it’s appropriate context (wrapping radio buttons) and FormGroup for all other grouping contexts in forms.

FormLabel

The Material-UI FormLabel is one of the simpler Form components.  It renders as a legend element with MuiFormLabel-root class applied.  Take a look at the DOM screenshot below:

Material-UI Form Label

The FormLabel has a surprising number of props.  They mostly related to inheriting state from the FormControl (disabled, required, etc) or styling (sx, color).

FormControlLabel

The FormControlLabel is a relatively simple element in the DOM, but it is complex in terms of MUI props. 

It renders as label with two children element spans.  The classes applied to the spans are what make them appear as a switch (in my example) and a nicely styled label.

Material-UI FormControlLabel

The FormControlLabel as an MUI component is complex because it has numerous props for styling, state, and controlling it’s child component.  A sample of props is below

  • Control – This accepts a component and renders it as the child control
  • Checked – Renders the control as checked or unchecked
  • Disabled – Disables the control and applies relevant ‘disabled’ styling to the label and control
  • Label – Text for the label
  • LabelPlacement – Option to place the label at the start, end, bottom or top relative to the control component

By default the label in FormControlLabel is created as a typography component (a span with Typography classes applied).  This can be disabled with the disableTypography prop.

FormLabel vs FormControlLabel

The FormLabel and FormControlLabel have similar names but are fundamentally different.  To recap, the FormLabel is simply a label but by default it has a more prominent styling.

The FormControlLabel is focused on controlling a child component while rendering a supporting label in an appropriate location.

FormHelperText

Material-UI’s FormHelperText is often used for supporting Inputs, but I used it to add additional information to my radio buttons.

Here’s a simple example of using FormHelperText: text appearing beneath an Input stating that the input is required. Here’s another example in the MUI docs.

The props of the FormHelperText component are all about keeping state in sync with a  FormControl and styling the FormHelperText accordingly.  Examples include disabled, error, and focused.

FormControlUnstyled

At the time of writing this article (Oct 2021), the docs for FormControlUnstyled component are simply a copy/paste of the FormControl docs.  The list of props does not appear accurate so I tested out a handful of props to see which are working.

Unfortunately, the only working prop seems to be the component prop.  When I pass fieldset, the DOM renders with a fieldset element and, interestingly, with the border in the below screenshot.  When I pass div, it renders with a div and no border.

Other props seemed to have no effect.  For example, the FormLabel and FormHelperText both look disabled regardless of setting disabled on FormControlUnstyled.  Furthermore, the radio buttons were not disabled when I set the disabled prop to ‘true’.

The variant prop also did not take effect regardless of which variant I passed.

Material-UI FormControlUnstyled

I believe the FormControlUnstyled is still evolving in the recently released MUI v5.  I expect that the final component will be intentionally reduced to the HTML form without any styling.  The purpose of this is for developers to have a clean slate to start from when styling the component.

React Form Component

React forms behave just like HTML forms.  By default they handle submits and can route to a new page on submit.  Wrapping the form subcomponents in a React form also gives semantic meaning to your page so that Google can better understand the purpose of your webpage.

If you need this kind of functionality, I highly recommend using a React Form.  If you don’t care as much about submit functionality, you could potentially wrap your form subcomponents with a div.  For basic forms, FormControl might be enough of a wrapper.

As a reminder, MUI interacts perfectly with native React components such as div and form.

Input Component

The Input component deserves it’s own article.  It is usually supported by several other form components.  In fact, this is such a common use case that MUI developed the TextField component, which is composed of an Input plus five other subcomponents.

The Input component is typically used for user text input.  It can be restricted to numeric, email, text only, or phone values.  There are a handful of third-party validation libraries, such as the popular react-material-ui-form-validator library.

React-Final-Form, Final-Form, and Final-Form-Material-UI

If you have a large form and need a high performance option, consider react-final-form.  It reduces the amount of components that rerender on changes to the form.  There is an integration dependency called final-form-material-ui that allows input MUI components (TextField, Checkbox, etc) to play well with react-final-form.  It was last updated in 2018 so I suggest testing it heavily with MUI v5.  In my experience it is still working correctly.

Resources

Code Sandbox Link

Share this post:

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.