Skip to main content
On this page

Form wraps a set of inputs and actions, provides error context to fields, and manages error display and focus.

When to use​

  • When collecting multiple pieces of information from a user in a single submission flow
  • When you need form-level error handling: an error summary listing field-level validation errors, or a general error for non-field failures such as an API timeout
  • When fields need to participate in automatic error context so each field can read its own error state by name

When not to use​

  • For a single standalone input with no submission flow - a Text Field or other field component works without a Form wrapper.
  • For read-only data display - use Summary List instead.
  • For a list of read-only key-value pairs - use Summary List instead.

Anatomy​

  1. Required fields notice ("Fields marked * are required", shown when the markRequiredFormFields theme option is enabled and hasMandatoryFields is true)
  2. Error summary (lists all field-level validation errors with links to each field; always rendered, visible only when errors is non-empty)
  3. General error (full-width error notification for non-field errors such as API failures; always rendered, visible only when generalError is set)
  4. Form fields (the FormFields container, which spaces direct children vertically)
  5. Form actions (the FormActions container, which places buttons in a horizontal row)

Basic usage​

Loading...
Edit live code
<Form>
  <FormFields>
    <TextField name="first-name" id="first-name" label="First name" />
    <TextField name="last-name" id="last-name" label="Last name" />
    <Checkbox name="terms" label="I agree to the terms & conditions" />
  </FormFields>
  <FormActions>
    <PrimaryCTAButton>Submit</PrimaryCTAButton>
    <SecondaryCTAButton>Clear</SecondaryCTAButton>
  </FormActions>
</Form>

Variants and features​

Error summary​

Pass an array of { id, message } objects to the errors prop. The ErrorSummaryNotification is always present in the DOM. When errors is non-empty it becomes visible and keyboard focus moves to it automatically.

Each id in the errors array must match the id of the corresponding field so that the error summary links correctly to the field. Nebula field components generate their own id, so pass the same name value to both the field and the error entry.

Loading...
Edit live code
() => { // ignore - for live code example only.
  const Example = () => {
    const [errors, setErrors] = useState([]);

    const onSubmit = (event) => {
      event.preventDefault();
      setErrors([
        { id: 'summary-first-name', message: 'Enter your first name' },
        { id: 'summary-last-name', message: 'Enter your last name' },
      ]);
    };

    return (
      <Form errors={errors} onSubmit={onSubmit} noValidate>
        <FormFields>
          <TextField name="summary-first-name" id="summary-first-name" label="First name" />
          <TextField name="summary-last-name" id="summary-last-name" label="Last name" />
        </FormFields>
        <FormActions>
          <PrimaryCTAButton type="submit">Show error summary</PrimaryCTAButton>
        </FormActions>
      </Form>
    );
  }

  return <Example />;
}

General error​

Use generalError for errors that are not tied to a specific field, such as an API timeout or a server error. Pass either title, message, or both. When generalError is set, a full-width error Notification appears below the error summary and focus moves to it.

Loading...
Edit live code
() => { // ignore - for live code example only.
  const Example = () => {
    const [generalError, setGeneralError] = useState(undefined);

    const onSubmit = (event) => {
      event.preventDefault();
      setGeneralError({
        title: 'We could not save your details',
        message: 'The service did not respond. Try again in a few minutes.',
      });
    };

    return (
      <Form generalError={generalError} onSubmit={onSubmit} noValidate>
        <FormFields>
          <TextField name="general-first-name" id="general-first-name" label="First name" />
        </FormFields>
        <FormActions>
          <PrimaryCTAButton type="submit">Show general error</PrimaryCTAButton>
        </FormActions>
      </Form>
    );
  }

  return <Example />;
}

Required fields notice​

When the markRequiredFormFields theme option is set to true, and the form contains at least one required field, the text "Fields marked * are required" appears at the top of the form. Set hasMandatoryFields={false} on forms where no fields are required to suppress this notice.

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

  return (
    <ThemeProvider theme={customTheme}>
      <Form>
        <FormFields>
          <TextField id="required-first-name" label="First name" />
          <TextField id="required-middle-name" label="Middle name" optional />
          <TextField id="required-last-name" label="Last name" />
        </FormFields>
        <FormActions>
          <PrimaryCTAButton>Submit</PrimaryCTAButton>
        </FormActions>
      </Form>
    </ThemeProvider>
  )
}

FormFields​

FormFields wraps form inputs and applies consistent vertical spacing between them using the betweenFormFields spacing token. Use it as the direct parent of all field components inside a form.

Direct children are spaced using the token, including non-field elements:

Loading...
Edit live code
<FormFields>
  <TextField name="spacing-first-name" id="spacing-first-name" label="First name" />
  <TextField name="spacing-last-name" id="spacing-last-name" label="Last name" />
  <div>
    <P>Example showing that a paragraph within a div receives the correct spacing.</P>
  </div>
</FormFields>

A P placed directly inside FormFields brings its own margins, which conflict with the token spacing. Wrap it in a div instead, as above:

Loading...
Edit live code
<FormFields>
  <TextField name="unspaced-first-name" id="unspaced-first-name" label="First name" />
  <TextField name="unspaced-last-name" id="unspaced-last-name" label="Last name" />
  <P>Example showing that a paragraph placed directly conflicts with intended spacing.</P>
</FormFields>

FormActions​

FormActions renders a row of buttons at the bottom of the form, using the betweenButtons spacing token. It applies a top margin of space[6] to separate it from the fields above.

The optional message prop renders a supplementary message above the button row, prefixed with an info icon. Use this for clarifying information that applies to the whole submission, such as a privacy notice or a note about processing time. Do not use it for validation errors.

Loading...
Edit live code
<FormActions message={<>Once accepted, it can take up to <Strong>30 seconds</Strong> to update this account</>}>
  <PrimaryCTAButton>Submit</PrimaryCTAButton>
  <SecondaryCTAButton>Clear</SecondaryCTAButton>
</FormActions>

Custom fields and useFormFieldError​

Nebula's own field components read their error from the form context using the useFormFieldError hook. Pass the field's name to the hook and it returns the matching error message string, or undefined if there is no error.

Note: useFormFieldError is not exported from the Nebula public API. It is documented here to explain how the field components pick up errors from a surrounding Form, not as an API for application code. In application code, pass the errors array to Form and let the Nebula field components resolve their own errors by name, as shown in Error summary.

// Internal to Nebula - not importable from '@kaluza-platform/nebula'.
const errorMessage = useFormFieldError(name);

Common form features​

Form fields share common behaviour for validation, hint text, required/optional marking, and field width. See the forms pattern guide for guidance on these topics.