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
ghostbutton.
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.