Field
Form field — the wrapper a control lives in: label above, helper or error below.
Beta · Forms · packages/css/src/components/field.css · React on Base UI: @pepperui/field
.pui-field · __label __hint __description __error · --error · --disabled. “(optional)” in the label, never an asterisk.
Sizes across, states down.
What it is for
Section titled “What it is for”- Label is a text property; an optional field says “(optional)” in its label, never an asterisk
- Description is off by default
- Error text appears in State=error, with the control in its own error state
- Three sizes (controls 28 / 36 / 44); states default, error, disabled — hover and focus belong to the control, not the wrapper
- Disabled drops the text to 0.5 opacity with the control
Use when any control that needs a visible label — one Field per control, stacked in a form with the form’s own gap.
Do not use when the control sits in a table cell or a toolbar where the column or the icon is the label; for a group of checkboxes or radios (that is a fieldset with a legend, coming with the Checkbox family).
Relates to Input (the default control — Select, Combobox, Textarea and Date picker drop into the same slot), Label, Input group.
Anti-patterns a placeholder standing in for the label · an asterisk for required · error text that repeats the label · a Field wider than its form column · two controls in one Field.
Keywords: field, form, label, helper, error, input wrapper
Figma — page Field: Field (9 variants)
shadcn: field
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 field.css.
<div class="pui-field"> <label class="pui-field__label" for="email">Email <span class="pui-field__hint">(optional)</span></label> <input class="pui-input" id="email" type="email"> <p class="pui-field__description">We reply within a day.</p> <p class="pui-field__error">Enter an email address.</p></div>The modifiers
Section titled “The modifiers”| What it is | Note | |
|---|---|---|
| State | --error shows __error and puts the control in its error state · --disabled dims the texts with the control (or :has(:disabled)) | — |
| Size | lives on the control (pui-input--small …); the label stays 14 at every size | — |
Rule — “(optional)” in the label, never an asterisk; error text says what to do, not what the label says.
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”| ink on ground | reads | floor | the matrix calls it | |
|---|---|---|---|---|
#e42521 on #ffffff |
4.57 | 4.5 | text.error on surface.page (promised) | ok |
#544d4d on #ffffff |
8.24 | 4.5 | text.muted on surface.page (promised) | ok |
#0b0202 on #ffffff |
20.51 | 4.5 | text.default on surface.page (promised) | ok |
3 distinct pair(s) rendered, 0 that axe calls a violation.
The focus ring
Section titled “The focus ring”3 of 3 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.