docs/react-v9/contributing/rfcs/react-components/convergence/field-custom-components.md
The FluentUI library currently has separate *Field components for each of the form controls that may go into a field, such as InputField and SliderField. This provides a simple API to include a label and validation message with any of the library's components.
One piece of feedback we've had for the Field controls is that it is difficult to use custom controls within a Field. It requires creating a custom control that uses the useField_unstable, etc. hooks, since there is no standalone Field component.
Field requires integration between its parts to ensure that the label, error message, and hint are all associated with the control.
Implement a single <Field> component, which does the layout for field, including slots for label, validationMesssage, and hint.
The control is the child, and Field adds the following props to the child using cloneElement (or a render function):
id (used as the label's htmlFor; generated if the child does not have an id already)aria-labelledbyaria-describedbyaria-invalid (when validationState="error" is set on the field)aria-required (when required is set on the field)Remove the existing InputField, SliderField, etc. controls, as it would be possible to put them inside the Field component.
Add add FieldContext around the children of Field to allow for better integration with built-in controls. For example, the Input control could have its size match the field's size prop by default.
<>
<Field label="FluentUI Input" validationState="error">
<Input defaultValue="..." />
</Field>
<Field label="Intrinsic input" validationState="error">
<input defaultValue="..." />
</Field>
<Field label="Custom component" validationState="error">
<MyInput defaultValue="..." />
</Field>
<Field label="Render props" validationState="error">
{props => (
<div>
<MyInput defaultValue="..." {...props} />
</div>
)}
</Field>
</>
<Input />, <Slider />, etc.), intrinsic elements (<input />), and custom components (<MyInput />).Tooltip, MenuTrigger, and other triggers apply props to children -- no new concepts in the library.*Field components.required prop on the child input when set on the Field; uses aria-required instead.id and include it on the props set using cloneElement?
htmlFor to be set by default, instead of relying on aria-labelledby. [How important is this?]id is added to a control without it being written out. Could theoretically have an effect on styling.id prop on its child, which is not possible when the child is a render function.makeField() to create custom Field componentsUses the Higher-Order Component approach to wrap a given component to surround it with Field.
Basically, it lets people make their own InputField-type component.
const MyInputField = makeField(MyInput);
<MyInputField label="..." validationState="error" defaultValue="..." />;
htmlFor instead of aria-labelledby by default.InputField, CheckboxField, etc.
<InputField size="large"> applies the size to both the Label and the Input.<div> or something, without encapsulating that in another wrapper component).<input> without a wrapper control.field() to create a context-aware component for FieldAdds a <Field> component similar to the main proposal, except it doesn't auto-apply props to child.
The wrapper field is similar to makeField, except the wrapper gets props from FieldContext and merges them on the child. Unlike makeField, it doesn't modify the render tree of the wrapped component.
const MyInputInField = field(MyInput, props => mapMyInputFieldProps(props));
<>
<Field label="..." validationState="error">
<MyInputInField defaultValue="..." />
</Field>
<Field label="..." validationState="error">
<div>
<MyInputInField defaultValue="..." />
</div>
</Field>
</>;
<div> is easy).Add <Field>, <FieldLabel> and <FieldMessage> components, with no "magic" prop settings. All relational attributes would need to be manually hooked up.
const id = useId('my-input');
<Field orientation="horizontal">
<FieldLabel htmlFor={id} required>
Example Field
</FieldLabel>
<MyInput id={id} defaultValue="..." required aria-invalid aria-describedby={`${id}-message ${id}-hint`} />
<FieldMessage id={`${id}-message`} state="error">
Input is invalid
</FieldMessage>
<FieldMessage id={`${id}-hint`}>Here's some hint text</FieldMessage>
</Field>;
InputField, etc.) vs. custom controls.