Design system

Forms

Labels above fields, errors under them, one primary action at the end.

<form onSubmit={save}>
  <TextInput label="Page name" value={name} onChange={(e) => setName(e.target.value)} />
  <TextInput
    label="Send a copy of each submission to"
    value={email}
    onChange={(e) => setEmail(e.target.value)}
    error={tried && !emailOk}
    errorMessage="Enter a work email, such as alex@meridian.com."
  />
  <Dropdown label="Send form submissions to" value={target} onChange={setTarget} options={targets} />
  <TextArea label="Meta description" rows={3} />
  <Toggle label="Search engines can list this page" checked={indexed} onChange={(e) => setIndexed(e.target.checked)} />
  <div className="form__actions">
    <Button variant="ghost" label="Cancel" onClick={onCancel} />
    <Button type="submit" variant="primary" label="Save settings" />
  </div>
</form>

Try submitting the example with an empty email to see the error.

Layout

  • One column. Labels sit above their field, never beside it.
  • 16px between fields, 24px between groups of fields.
  • Group related fields under a short heading when a form has more than five or six.
  • Actions sit at the bottom right: the primary action last, Cancel before it as a ghost button.

Validation

  • Validate when the person submits, then again as they fix each field. Do not show errors while they are still typing for the first time.
  • Put the error under the field it belongs to, in one sentence that says how to fix it.
  • Keep the submit button enabled so people can find out what is missing. Disable it only while the form is saving.

Labels and placeholders

  • Every field has a visible label. A placeholder is a hint, such as an example value, never the label.
  • Mark the optional fields, not the required ones, when most fields are required.

Saving

  • Show progress on the button itself ("Saving…") and disable it while saving.
  • Confirm success with a toast when the form closes, or inline when it stays open.

On this page