MobTranslate styleguide

Fieldset

Group related native controls under a meaningful legend and preserve their values through temporary unavailability.

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

PartActual contract
FieldsetNative fieldset, zero minimum inline size, 16px padding, token border/radius; HTMLFieldSetElement wrapper ref
FieldsetLegendNative legend, text-base / 700 with 4px inline padding; label for group
Inner layoutCaller-owned controls/layout. Small screens should keep logical reading order and bounded widths.
Disabled groupNative 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

Local example details

No grouped native payload inspected.

tsx
'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 / partContract
Fieldset disabled / name / formNative fieldset attributes. Disabled descendant successful controls are excluded from native FormData.
FieldsetLegend childrenVisible group title; native legend attributes and HTMLLegendElement wrapper ref.
className / style / render / native propsInstalled 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.