Not surprisingly, the core of the Composable Form Specification is the "Form" component. Any component exported by a package can be used as a "Form" component if it properly implements the specification.

When using reacto-form, code that uses a simple empty Form component looks something like this:

Note that only one thing changed here: reacto-form package name became other-form. Because both components conform to the specification for a Form-type component, I can rely on my form to continue working without any further changes.

(In reality, competing components may have other non-spec properties that differ, but the spec attempts to cover as many important properties as possible.)

A Form component is really just a group of inputs resulting in a single JavaScript object. As such, you can put forms within forms. Forms that have parent forms will track and gather their descendant input values just as top-level forms do, but they'll delegate validation and submission to the top-level form.

A nested form must have a name property with an object path notation string like an input.

Note that object path notation begins from the closest form, so the "city" input value is saved to the path addresses[0].city in the onSubmit data.

Packages can choose to export entire premade forms as long as they conform the the specification for Form-type components. So for example, you might be able to plug in an open source address form component like this:

A FormList maps to an array in your data structure, that is, some arbitrary number of similar items that can be added, edited, and removed.

When using a FormList, you include children that serve as the template for each item in the list. Among these children can be at most one ErrorsBlock and exactly one item data element, which is either an input (for an array of scalar data) or a form (for an array of objects).

When using an ErrorsBlock as a child of FormList, omit the names property. When using an Input or Form as a child of FormList, omit the name property. These properties are added by FormList, which specifies the correct index for each item.

Here is a twist on an earlier example, if we wanted to let a user enter any number of addresses:

The Form component handles validation automatically in the following ways:

When the form data needs to be validated, it is passed to the function provided in the form's validator property. That function will return an array of error objects or a Promise that eventually resolves with an array of error objects.

The error objects that are relevant to a certain component within the form are passed to it automatically, in the errors property. Relevancy is determined based on the name or names property of the component matching the name property of the error object.

Fields, Inputs, FormLists, and child Forms can all use the errors passed in if necessary, for example to show indicators or change colors. But the most common case of using errors is with the ErrorsBlock component.

An ErrorsBlock simply reads the message property of each error object passed in and displays all the messages to the user. The messages are assumed to be translated into the user's language by the validator function.

You'll often want to apply certain styles, classes, or other props to all instances of certain form components using anywhere in your app. A good pattern for this is to import from the form packages in a single file, change the defaultProps on the components, and then export them. Everywhere you need them throughout your app, you can then import from this file rather than directly from the form packages.

Here's an example of using this pattern to apply Bootstrap classes to ReactoForm components: