Skip to main content
On this page

Forms

Learn to build forms that follow best practices.

View for LLMs

Forms represent the most meaningful interactions we have with our users. These guidelines serve to summarise the wealth of existing research. When it comes to forms, simple is best.

This pattern is guidance, not a rigid prescription. It brings together what existing research and our own products have shown works, so treat it as a strong starting point and adapt it where your context genuinely differs.

Example​

Loading...
Edit live code
() => { // ignore - for live code example only.
  const isValidEmail = (value) => /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(value);

  const Example = () => {
    const [errors, setErrors] = useState([]);

    const onSubmit = (e) => {
      e.preventDefault();
      const newErrors = [];

      const {
        name,
        email,
        terms
      } = Object.fromEntries(new FormData(e.currentTarget));

      if (!name) {
        newErrors.push({ id: 'name', message: 'Enter a name' });
      }
      if (email && !isValidEmail(email)) {
        newErrors.push({ id: 'email', message: 'Enter a valid email address' });
      }
      if (!terms) {
        newErrors.push({
          id: 'terms',
          message: 'You must agree to the terms & conditions',
        });
      }

      setErrors(newErrors);

      if (!newErrors.length) {
        alert('success');
      }
    }

    return (
      <>
        <Heading1>Example form</Heading1>
        <Form errors={errors} onSubmit={onSubmit}>
          <FormFields>
            <TextField
              id="name"
              name="name"
              label="Name"
            />
            <EmailField
              id="email"
              name="email"
              label="Email"
              optional
            />
            <Checkbox
              id="terms"
              name="terms"
              label="I agree to the terms & conditions"
            />
          </FormFields>
          <FormActions>
            <PrimaryCTAButton type="submit">Submit</PrimaryCTAButton>
          </FormActions>
        </Form>
      </>
    );
  }

  return <Example />;
}

Collecting information​

It's important to ask the right questions so that everyone can use and complete the form as quickly as possible. GOV.UK has some great guidance on this.

  • Always ask for only information you definitely need.
  • Always put the questions in a sensible order.
    • For example, put payment details after delivery details.
  • Always start with questions that will let users know if they’re not eligible.
    • Avoids wasting people's time.

Structuring form layout​

When structuring forms, start simple.

The Stack component should be used to control the vertical spacing between form fields.

Grouping form fields with fieldsets​

Fieldsets can be used to group related form fields, for example address or contact information. A legend element describes the group of inputs and should always be the first element within a fieldset.

Form fields do not always need to be grouped but if they are this should always be done using a fieldset.

There is no fieldset component. Instead use the Card, Stack, and Heading components and change the semantic elements using the as prop from Styled Components.

There is an example of this in the Card component documentation.

Hint text​

  • Always use hint text to tell users why you’re asking them for certain information if it’s not obvious.
  • Always use hint text when users are more likely to make a mistake.
    • Such as having to satisfy a complex set of password rules.

Fields​

The way we design our fields can help inform users what is being asked of them and why. We have tried to build as much best practice into our form components as possible.

Labels​

  • Always give fields a label.
    • Sighted users can see them, visually-impaired users can hear them and motor-impaired users can more easily set focus to the related input.
  • Never use placeholders.

Required and optional fields​

Required field marking is controlled by a transitional theme property. Learn how to enable it in your app.

Fields are required by default. Mark optional fields using the optional prop, which suppresses the required indicator on that field.

Loading...
Edit live code
() => {
  // ignore - for live code example only.
  const customTheme = createTheme({
    config: {
      markRequiredFormFields: true,
    },
  });

  return (
    <ThemeProvider theme={customTheme}>
      <Form>
        <FormFields>
          <TextField id="name" label="Name" />
          <EmailField id="email" label="Email" optional />
        </FormFields>
        <FormActions>
          <PrimaryCTAButton type="submit">Submit</PrimaryCTAButton>
        </FormActions>
      </Form>
    </ThemeProvider>
  )
}

The Form component automatically renders a "Fields marked * are required" key at the top of the form. If a form contains no required fields, set hasMandatoryFields={false} to suppress it.

If you are rendering fields outside the Form component, include the key text manually at the top of the form.

Disabled states​

Nebula's fields and buttons don't have disabled states. The main reason for this choice is that from accessibility point of view disabled buttons give a poor user experience. In most cases screen readers skip the field because it isn’t focusable and keyboards won’t register it, and in other cases the contrast on the component is so low it can’t be read, rendering it unclear on what the user needs to do.

For buttons, we recommend using the default state and then relying on error handling in your form, this allows the user to get adequate feedback on what they need to do if they have missed something.

For form fields, we recommend reframing the problem. Is there a better way to design the experience to remove the need for a deactivated state? For example a great way to communicate with the user on why something isn’t available is through descriptive copy.

Because you have told us you only want green energy, you cannot select coal as an energy source.

If you would like to select non-green energy instead, you can update your preferences here.

In the instance where you have a form field that requires additional information when in a particular state, we advise using a conditional reveal.

Validating data​

Many of the principles above serve to help users avoid validation issues, but inevitably some users will encounter errors. It’s important we make these as painless and easy to fix as possible.