4.1 KiB
Forms
Dify UI form primitives compose Base UI's native form semantics, field accessibility, and Dify styling. They are not a form state-management or schema framework. See the Base UI forms handbook for the upstream model.
Submit boundary
Every group of controls that saves or submits together needs a real <form> boundary. Do not wire
an Input and a click-only Button together as an informal form.
Use Form when the Dify UI boundary should own Base UI's structured onFormSubmit values,
consolidated errors, actionsRef, or validationMode. It renders a native <form>. A native form
remains correct when another form library owns submission and validation; do not nest form owners.
Set Button submit buttons to type="submit" explicitly. Keep every other button inside a form
at type="button".
Value and state ownership
Form owns the submission and validation boundary described above, not each field's draft.
Choose controlledness from source-of-truth needs, independently from where a draft is stored.
Prefer defaultValue when application React code does not need to own the current value. Use
value and change handlers when application rendering or coordination must own it while editing.
Listening to change events, tracking dirty state, and native or primitive validation do not by
themselves require controlled state.
Application code owns the draft in the narrowest component whose lifetime matches it. A value can be controlled locally without being lifted. An uncontrolled field can participate in a persisted workflow when that workflow captures its value at an explicit persistence boundary. The surrounding surface defines its mount lifecycle; owner placement determines whether draft state lives inside or outside that lifecycle.
Fields and labels
Use Field when a control needs a shared name, label, validation, description, or error state. A
standalone Input may use a native <label htmlFor> relationship, but normal form rows should
prefer a visible label. FieldDescription and FieldError provide the corresponding accessible
message relationships.
Choose the label primitive by the control:
- Text-like inputs,
Textarea, input-basedComboboxandAutocomplete, a singleCheckbox, eachRadiooption,Switch, andNumberFielduseFieldLabel. - Trigger-based
Selectfields useSelectLabel. Sliderfields follow the Base UI Slider anatomy and useSliderLabel; only multi-thumb sliders add per-thumbaria-labelto distinguish the thumbs.SelectGroupLabelandAutocompleteGroupLabellabel option groups inside popup content. They are not field labels.
Use InputGroup when a prefix, suffix, or action shares the input's visual surface.
Grouped controls
Use Fieldset and FieldsetLegend when one field contains related controls, such as checkbox or
radio groups, multi-thumb sliders, or a section of related inputs. Wrap each checkbox or radio
option with FieldItem and give it its own label:
<Field name="allowedNetworkProtocols">
<Fieldset render={<CheckboxGroup />}>
<FieldsetLegend>Allowed network protocols</FieldsetLegend>
<FieldItem>
<FieldLabel className="flex items-center gap-2">
<Checkbox value="https" />
HTTPS
</FieldLabel>
</FieldItem>
</Fieldset>
</Field>
Fieldset owns group semantics and the legend relationship, not interactive state. Pass
disabled, value, defaultValue, and change handlers to the group primitive.
Every radio belongs to a RadioGroup. Use FieldsetLegend to name the group and FieldLabel to
name each option; do not render a standalone Radio.
Keep form state, schemas, server validation, and reset behavior outside the primitive internals, in the nearest application owner with the required lifetime. Pass observable state through the public field and control props instead of replacing the semantic structure.