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).
Sizes across, states down.
What it is for
Section titled “What it is for”- 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 markup — copy this
Section titled “The markup — copy this”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>The modifiers
Section titled “The modifiers”| 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
Accessibility — measured, not assumed
Section titled “Accessibility — measured, not assumed”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.
Contrast
Section titled “Contrast”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.
The focus ring
Section titled “The focus ring”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.
Keyboard
Section titled “Keyboard”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.