FormControl keeps the visible and semantic parts of one field together. Use it
when a text field, select, combobox, or other control needs a label, required
state, helper text, or validation message.
The wrapped control still owns its value and actions. FormControl owns the
surrounding field anatomy and relates that anatomy to one unambiguous control.
Example
use fission::prelude::*;
let email_field: Widget = FormControl {
id: Some(WidgetId::explicit("account.email-field")),
label: Some("Email address".into()),
child: TextInput {
id: Some(WidgetId::explicit("account.email-input")),
value: view.state().email.clone(),
on_input: Some(update_email_action),
..Default::default()
}
.into(),
error: view.state().email_error.clone(),
helper: Some("Used for receipts and account alerts.".into()),
required: true,
}
.into();
When error is present it replaces the helper message, marks the related
control invalid, and supplies that validation message to accessibility output.
Use FormControlLayout when the anatomy needs localized text, custom retained
presentation, stable region identities, or a helper that remains visible beside
an error:
use fission::prelude::*;
let mut email = FormControlLayout::new(
WidgetId::explicit("account.email-field"),
TextInput {
id: Some(WidgetId::explicit("account.email-input")),
value: view.state().email.clone(),
on_input: Some(update_email_action),
..Default::default()
},
)
.label(
FormLabel::custom(
TextContent::KeyWithFallback {
key: "account.email.label".into(),
fallback: "Email address".into(),
},
email_label_node,
)
.semantics_identifier("account.email.label"),
)
.description(
FormDescription::custom(
"Used for receipts and account alerts.",
receipt_explanation_node,
)
.semantics_identifier("account.email.description"),
)
.required(true);
if let Some(message) = &view.state().email_error {
email = email.error(
FormError::new(message.clone())
.semantics_identifier("account.email.error"),
);
}
let email_field: Widget = email.into();
The text passed to FormLabel::custom, FormDescription::custom, and
FormError::custom remains the accessible text; the retained child controls
only visual presentation. Standard constructors render the text with the active
field-label or helper recipe.
Anatomy and visual states
The shared recipe arranges these regions in this vertical order:
an optional label, including a visible * when required is true;
the supplied child control;
an optional helper description; and
an optional validation error.
The compact FormControl chooses either its error or its helper. The named
FormControlLayout can retain both: both describe the field, while only
FormError supplies invalid state and the authoritative validation message.
When a custom label child is supplied, that child owns the visual required
marker; semantic required state is still propagated to the control.
Spacing comes from the active spacing tokens. Label and message typography use
the label and helper styles from theme.components.text_input, which keeps the
field anatomy consistent with TextInput. In error state, the visible label and
message use the semantic error color. The child keeps its own layout and visual
recipe; configure validation on that child too when its own painted border or
surface must show an invalid state.
Field reference
| | | |
|---|
| | Stable identity for the complete field group. | When omitted, Fission derives an implicit identity from the retained build path. |
| | Visible and semantic field label. | The spoken label excludes the visual required marker. |
| | The input or choice control. | Required. Its value and actions remain controlled by the application. |
| | Current validation message. | Takes precedence over helper and marks one related child control invalid. |
| | Guidance shown while there is no error. | Omit it when the label and context are sufficient. |
| | Whether the field is required. | Adds the visible marker and semantic required state. |
The named anatomy has these responsibilities:
| |
|---|
| Owns one required control plus optional label, description, error, and semantic required state. Its id is required. |
| Supplies accessible label text, optional custom retained presentation, and optional semantic ID and identifier. |
| Supplies persistent supporting text related through described_by, with optional custom presentation and identity. |
| Supplies a validation message, invalid state, described_by relationship, and optional custom presentation and identity. |
Create named anatomy with FormControlLayout::new(id, control), then add
regions with .label(...), .description(...), .error(...), and
.required(...). Each text region has new(text) and custom(text, child)
constructors plus .id(...) and .semantics_identifier(...). If an explicit
region ID is unnecessary, the layout derives a stable one from its own ID.
Semantic relationships
The label and current messages receive stable identities derived from the
layout id unless explicit IDs were supplied. The wrapper exposes labelled_by
and described_by relationships, and those relationships are also added to the
single eligible form-control semantic node inside child. Existing child
relationships, identity, actions, value, selection, and text-editing behavior
are preserved. When named anatomy contains both a description and an error,
both IDs appear in described_by; only the error marks the control invalid.
Eligible roles are text input, generic input, combobox, checkbox, radio,
switch, and slider. This includes the actual trigger inside Select and the
editable input inside Combobox, rather than stopping at an outer layout node.
Required state is additive. When error is present, that control exposes an
invalid validation state and the same validation message.
If the child subtree contains no eligible control or more than one, Fission
does not guess which descendant owns the label. The wrapper still exposes its
own relationships, but the descendants are left unchanged. Use one
FormControl per field instead of wrapping a group of unrelated inputs.
Keyboard, responsive, and target behavior
FormControl does not insert a focus stop or intercept keys. The wrapped
control owns Tab, editing, selection, popup, and activation behavior. The
label is descriptive text rather than an independently clickable focus target.
The column follows its parent's width and does not choose a breakpoint. Test
long labels and validation messages, increased text scale, and narrow layouts.
FormControl registers no motion itself. Any motion composed around the field
still follows the global Env::motion_preference, so the control does not need
its own preference branch.
Visible content and relationships lower through the shared target model.
Static site and SSR output can emit the labelled, described, required, and
invalid field contract; changing values or validation state still requires an
interactive runtime or browser island.
Production checklist
Wrap exactly one field and give dynamic or reorderable fields a durable id.
Keep helper text useful and make validation messages explain how to recover.
Drive error from application validation state rather than local paint-only
conditions.
Configure the child control's validation presentation too when its surface
should visibly enter an error recipe.
Verify the resolved accessible name, description, required state, and invalid
message on the real target bridge.