Skip to content

Form

Form — the form itself: its card, a title, the fields, the consent, one primary Button, and the result that takes their place.

Beta · Forms · packages/css/src/components/form.css

.pui-form in a .pui-card (--lined · --raised · --plain) · __trap · .is-success shows the Form result in the fields’ place · the file control .pui-input--file (__file-name __file-icon) · the Button sending aria-busy="true" (request 34).

Form — every size and every stateOpen in the workshop

Sizes across, states down.

  • The order of parts, always: the Title (the Card’s title — body/large at Medium) › the spam trap (never shown) › the Fields, one per control: label › control › message › the consent — a Checkbox in a Field, with its message › ONE primary Button at full width › the Form result
  • The column’s gap is space/24
  • Card lined / raised / plain — the card is part of the form: lined is the tinted card with the 2 px line, raised the tinted card lifted by elevation/card with no line (a card on a white page), plain the white card (on a tinted band — its result’s slot turns grey, the inversion rule). The inset is the form’s: space/32, space/24 on a phone, where a card’s own is space/20
  • State sent — the parts leave and the Form result shows in their place, inside the same card; a failure is an Alert above the Button, never the result
  • The file control is the Input as a label around the native file input: the file’s name in the caption ink (‘No file chosen’ until one is), a paperclip at its end (Input with Has icon trailing)
  • Sending: the Button between the press and the answer is dimmed and cannot be pressed twice; its label says ‘Sending…’
  • ‘(optional)’ in a label, never an asterisk; an error says what to do
  • In code: .pui-card › form.pui-form › .pui-card__title · .pui-form__trap · .pui-field … · .pui-button.pui-button--block · .pui-form-result; .is-success on the form; aria-busy on the Button; .pui-input--file with __file-name and __file-icon

Use when a page or a screen asks for something and sends it — a contact form, a demo request, an application, a settings form.

Do not use when the input is one field with its button (Input group — the newsletter field); the questions come one at a time (Questionnaire); the fields filter what is on screen (a filter row).

Relates to Card, Field, Input, Textarea, Select, Checkbox, Button, Form result, Alert, Input group, Questionnaire.

Anti-patterns two primary actions · a form without its card · the consent as bare text beside a box · the result under the fields instead of in their place · a placeholder used as the label · an asterisk for required · a per-page form with its own class family · the Button at its own width in a column form.

Keywords: form, contact form, lead form, fields, consent, submit, sending, file upload, form card

Figma — page Form: Form (4 variants)

The class, the parts, and nothing that is not in the package. The same block opens form.css.

<div class="pui-card pui-card--lined"><form class="pui-form" novalidate>
<h3 class="pui-card__title">Get in touch with us to know more</h3>
<input class="pui-form__trap" type="text" name="surname" tabindex="-1" autocomplete="off" aria-hidden="true">
<div class="pui-field"><label class="pui-field__label" for="f-name">Name</label>
<input class="pui-input" id="f-name" required><span class="pui-field__error">Please enter your name</span></div>
<div class="pui-field"><span class="pui-field__label">CV</span>
<label class="pui-input pui-input--file"><span class="pui-input__file-name">No file chosen</span>
<span class="pui-icon pui-input__file-icon"><svg><use href="icons.svg#paperclip"/></svg></span>
<input type="file"></label></div>
<div class="pui-field"><label class="pui-checkbox"> …the consent… </label><span class="pui-field__error">…</span></div>
<button class="pui-button pui-button--block" type="submit">Book a demo</button>
<div class="pui-form-result" role="status" aria-live="polite"> … </div></form></div>
What it is Note
State .is-success on the form — the parts leave and the Form result shows in their place aria-busy=“true” (with disabled) on the Button — sending —
Parts __trap — the spam trap, never shown · .pui-input--file with __file-name and __file-icon —

Rule — one primary action; the order of the parts never changes; a failure is an Alert above the Button, not the result

Every number here is measured on the rendered component: contrast (npm run contrast:render), the focus ring (npm run focus) and the keyboard (npm run keyboard). The floors are principle 12’s.

No story of this component renders a text pair axe can measure — a component made of surfaces and borders rather than words. The token pairs it uses are in artifacts/contrast-matrix.md.

6 of 6 focus stop(s) in this block meet WCAG 2.4.13 on screen — the same crop screenshotted focused and unfocused, and the difference measured.

Nothing in this component claims an ARIA role with a keyboard contract; its controls are ordinary ones and the browser already gives them their keys.

Tab order across the whole library is document order over 344 stops, and Shift+Tab is its exact reverse — no keyboard trap anywhere (WCAG 2.1.2).

Pepper UI v1.5.0

The Mobupps design system. 386 tokens, 88 components, 65 pages in the workshop — every page of this site generated from the repository, so nothing here is typed twice.