Fieldset
Group related native controls under a meaningful legend and preserve their values through temporary unavailability.
Choose a related group
Use Fieldset when the legend gives several controls shared meaning, such as contact details or a choice group. Use Field for an individual control. Fieldset supplies native grouping and disabled behavior, not individual validation, permission or submission.
Prefer a short legend naming the question or topic. Keep supporting instructions near the group and use separate labels for every control. Avoid grouping unrelated page sections merely to draw a border.
Anatomy and specifications
| Part | Actual contract |
|---|---|
| Fieldset | Native fieldset, zero minimum inline size, 16px padding, token border/radius; HTMLFieldSetElement wrapper ref |
| FieldsetLegend | Native legend, text-base / 700 with 4px inline padding; label for group |
| Inner layout | Caller-owned controls/layout. Small screens should keep logical reading order and bounded widths. |
| Disabled group | Native descendant disabled behavior and installed disabled state; first legend has native special rules. The wrapper supplies the native disabled attribute in its default fieldset render; custom renderers must preserve it. Keep recovery outside the disabled group. |
Working native group and payload
'use client';
import { useState } from 'react';
import { Button, Field, FieldControl, FieldDescription, FieldError, FieldLabel, FieldValidity, Fieldset, FieldsetLegend, Form, Input } from '@mobtranslate/ui';
export function RegisteredFieldExample() {
const [disabled, setDisabled] = useState(false);
const [result, setResult] = useState('No registered local value submitted.');
return <div data-field-lab className="space-y-4">
<Form onFormSubmit={values => setResult(`Registered local value: ${String(values.title ?? 'omitted')}. No data was sent.`)}>
<Field name="title" disabled={disabled} validate={value => typeof value === 'string' && value.trim().length >= 3 ? null : 'Use at least three non-space code units for the local title.'} className={state => state.valid === false ? 'local-field-invalid' : undefined}>
<FieldLabel>Local example title (required)</FieldLabel>
<FieldControl data-registered-title required render={<Input />} />
<FieldDescription>This control registers its value and description with Field.</FieldDescription>
<FieldError />
<FieldValidity>{validity => <p data-field-validity>Validation: {String(validity.validity.valid)}; current value: {String(validity.value ?? '')}.</p>}</FieldValidity>
</Field>
<Button type="submit">Submit registered field</Button>
</Form>
<Button type="button" variant="outline" onClick={() => setDisabled(current => !current)}>{disabled ? 'Enable registered field' : 'Disable registered field'}</Button>
<p role="status">{result}</p>
</div>;
}
export function FieldsetInputExample() {
const [disabled, setDisabled] = useState(false);
const [result, setResult] = useState('No grouped native payload inspected.');
return <form data-fieldset-lab className="space-y-4" onSubmit={event => {
event.preventDefault(); const data = new FormData(event.currentTarget);
setResult(`Native payload: ${JSON.stringify(Object.fromEntries(data))}. No data was sent.`);
}}>
<Fieldset disabled={disabled} className={state => state.disabled ? 'local-fieldset-disabled' : undefined}>
<FieldsetLegend>Local example details</FieldsetLegend>
<div className="space-y-4"><label className="block" htmlFor="group-title">Local title</label><Input id="group-title" name="groupTitle" defaultValue="Reading sample" /><label className="block" htmlFor="group-note">Local reference</label><Input id="group-note" name="groupReference" defaultValue="Example only" /></div>
</Fieldset>
<div className="flex flex-wrap gap-3"><Button type="button" variant="outline" onClick={() => setDisabled(current => !current)}>{disabled ? 'Enable grouped details' : 'Disable grouped details'}</Button><Button type="submit">Inspect grouped payload</Button></div>
<p role="status">{result}</p>
</form>;
}
API reference
| Prop / part | Contract |
|---|---|
| Fieldset disabled / name / form | Native fieldset attributes. Disabled descendant successful controls are excluded from native FormData. |
| FieldsetLegend children | Visible group title; native legend attributes and HTMLLegendElement wrapper ref. |
| className / style / render / native props | Installed Base UI contracts. State callbacks are preserved by styled wrappers. Custom elements must retain semantics, handlers and refs. |
Keyboard, state and submission
- The fieldset is normally not a focus stop; its controls retain native focus order. Legend context should be announced with a named screen-reader/browser pair.
- A disabled group retains DOM values while suppressing descendant editing. Native controls may match :disabled without their individual disabled property being true.
- Keep enable/cancel/recovery actions outside the disabled group. Verify both values and submitted native payload before and after enabling.
- Nest groups only when the hierarchy helps understand a task. Native grouping is not an independent saving or authorization boundary.
Failures and recovery
- A div with bold text does not provide a native legend relationship. Do not remove a meaningful legend just to compress the layout.
- Native fieldset disabling has a first-legend exception. Do not place critical mutable task controls in that exception accidentally.
- Disabling a group to indicate pending may remove data from native submission. Capture the payload first or retain read-only controls according to the task.
Verification and release scope
- Verify the actual label/control connection, description/error IDs, invalid state, focus order and corrected input with pointer and keyboard. Read the submitted payload rather than inferring it from visible text.
- Test blank, populated, invalid, disabled, read-only and pending/recovery states. Check both themes, long labels, user text spacing, 320px reflow, reduced motion, forced colors and zoom.
- Run the named input-family browser checks and source/type/build checks. Inspect mobile keyboards, autofill, password managers and a named screen-reader/browser pair separately. DOM tests do not prove their behavior.
- Client validation supports correction; it does not establish authorization, consent, source authority, language quality, identity, security or a successful server write.