Skip to main content

Getting started with webbloqs Forms

Installation

To install the webbloqs package, run the following command:

pnpm add @webbloqs/react

Usage

To build a form with webbloqs, import Form and some form elements from the @webbloqs/react package.

The components from @webbloqs/react are Web Components, and they rely on the ElementInternals interface to associate form elements with their form.

For this reason you must set a matching formId property on the Form and a matching form property on each contained form element.

import { CheckboxField, DropdownField, Form, Layout, RadioField, TextField } from '@webbloqs/react';

const FORM_ID = "basic-form";

<Form formId={FORM_ID}>
<RadioField
form={FORM_ID}
name="salutation"
options={[
{ label: "Mr", value: "mr", selected: true },
{ label: "Mrs", value: "mrs" },
]}
/>
<Layout distribution="1|1">
<TextField form={FORM_ID} name="firstname" label="First Name" required/>
<TextField form={FORM_ID} name="lastname" label="Last Name" required/>
</Layout>
<DropdownField
form={FORM_ID}
name="country"
label="Country"
options={[
{ label: "Switzerland", value: "ch", checked: true },
{ label: "Germany", value: "de" },
{ label: "France", value: "fr" },
]}
/>
<CheckboxField
form={FORM_ID}
name="newsletter"
options={[{ label: "I want to receive the newsletter", value: "yes" }]}
/>
</Form>;

See this form in action in the webbloqs catalog, and Form Elements for the full list of elements.

Form comes with a predefined submit button, its label can be customized as follows:

<Form submitLabel="Create Account">
{/* form elements as above */}
</Form>

Learn about additional options to customize the form's appearance and behaviour in the TODO advanced usage section.

Data Handling and Submission

A Form automatically gathers all associated form values and provides them to the handleSubmit callback as a FormData object. You can determine that form submission was successful if the promise resolves, in that case the content of the result slot will be displayed.

The standard HTML validation can be used to validate user input. To prevent submission you can simply return a rejected Promise in your handleSubmit callback, in that case you may display a custom error message to the user by specifying the content of the error slot.

<Form handleSubmit={(event: InputEvent, formData: FormData): Promise<void> => {
console.log([...formData.entries()]);
return Promise.resolve();
}}>
{/* form elements as above */}
<div slot="result">Your account has been created successfully.</div>
<div slot="error">An error occurred, please try again later.</div>
</Form>

Advanced Form Concepts

Multistep Forms

Multistep forms are a common pattern in web applications, allowing users to fill out complex forms in a more manageable way, the webbloqs FormStep component can be used in conjunction with Form to create such a multistep form.

Each FormStep must be identified by a unique id for proper form data collection and can have an optional title. The form will then automatically show a configurable "next" and "previous" button to navigate between the steps.

import { CheckboxField, Form, FormStep, Layout, TextField } from '@webbloqs/react';

const FORM_ID = "conditional-fields";

<Form formId={FORM_ID}>
<FormStep id="user-info" title="User Info">
<Layout distribution="1|1">
<TextField form={FORM_ID} name="firstname" label="First Name" required />
<TextField form={FORM_ID} name="lastname" label="Last Name" required />
</Layout>
</FormStep>
<FormStep id="address-data" title="Address Data">
<TextField form={FORM_ID} name="street" label="Street" required />
<Layout distribution="1|1">
<TextField form={FORM_ID} name="postalcode" label="Postal Code" required />
<TextField form={FORM_ID} name="city" label="City" required />
</Layout>
</FormStep>
</Form>

Prefilling Form Data

Especially in multistep forms, it is often useful to prefill form data from a previous step or from an external source. There are essentially two ways to prefill form data in webbloqs, analogous to how React handles controlled and uncontrolled components.

  1. Uncontrolled Input Fields: The recommended way with webbloqs is to use the defaultValue property on inputs, which allows you to set an initial value without controlling the field's state and later retrieve updated values via FormData.

    const FORM_ID = "prefill";

    <Form formId={FORM_ID} handleSubmit={(event: InputEvent, formData: FormData): Promise<void> => {
    console.log([...formData.entries()]);
    return Promise.resolve();
    }}>
    <RadioField
    form={FORM_ID}
    name="salutation"
    label="Salutation"
    options={[
    { label: "Mr", value: "mr" },
    { label: "Mrs", value: "mrs" },
    ]}
    defaultValue="mr"
    />
    <TextField form={FORM_ID} name="firstname" label="First Name" defaultValue="John" />
    <TextField form={FORM_ID} name="lastname" label="Last Name" defaultValue="Doe" />
    </Form>
  2. Controlled Input Fields: In cases where your field values are controlled via React state for any reason you can instead define the value property on the form field directly to reflect its initial and updated state in the DOM.

    const [user, setUser] = useState({ salutation: "mr", firstName: "John", lastName: "Doe" });

    <Form handleSubmit={() => {
    console.log(user);
    return Promise.resolve();
    }}>
    <RadioField
    name="salutation"
    label="Salutation"
    options={[
    // Note: The `selected` property is deprecated in favor of defaultValue
    { label: "Mr", value: "mr", selected: user.salutation === "mr" },
    { label: "Mrs", value: "mrs", selected: user.salutation === "mrs" },
    ]}
    onChange={(event) => setUser((prevUser) => ({...prevUser, salutation: event.target.value }))}
    />
    <TextField
    name="firstname"
    label="First Name"
    value={user.firstName}
    onInput={(event) => setUser((prevUser) => ({...prevUser, firstName: event.target.value }))}
    />
    <TextField
    name="lastname"
    label="Last Name"
    value={user.lastName}
    onInput={(event) => setUser((prevUser) => ({...prevUser, lastName: event.target.value }))}
    />
    </Form>

Conditional Fields

With the condition property on FieldGroup webbloqs provides a mechanism to set field state based on the value of other fields.

Both the condition source and the FieldGroup itself must be part of the same form, wired up via their form property.

PropertyTypeFunction
conditionSourceNamestringDeclare the field name for which this condition applies.
predicatefunctionProvide a function that is called whenever the condition source value is updated.
getTargetfunctionOptional method to define a different target for the condition than the FieldGroup.
import { CheckboxField, FieldGroup, Form, TextField } from '@webbloqs/react';

const FORM_ID = "conditional-fields";

<Form formId={FORM_ID}>
<CheckboxField
form={FORM_ID}
name="show-section"
options={[
{
label: "Show section",
value: "on",
},
]}
/>
<FieldGroup
form={FORM_ID}
condition={{
conditionSourceName: "show-section",
predicate: (value, { setVisible }) => setVisible(value === "on"),
}}>
<TextField form={FORM_ID} name="firstname" label="First Name" required />
<TextField form={FORM_ID} name="lastname" label="Last Name" required />
</FieldGroup>
</Form>

As you can see the predicate function receives the current value of the condition source field and a set of methods to control the FieldGroup's behaviour. The provided methods are as follows:

MethodFunction
setVisibleShow or hide the whole FieldGroup
setEnabledEnable or disable all fields in the FieldGroup

Custom Field Validation

With the onValidate property on form elements you can easily add custom validation logic to your form fields. This property accepts a function that receives the current value of the field and returns either a string with your custom error message if the value is invalid or null otherwise.

Note that standard validations like required will still apply after your custom validation.

import { Form, TextField } from '@webbloqs/react';

const FORM_ID = "custom-validation";

<Form formId={FORM_ID}>
<TextField
form={FORM_ID}
name="phone"
label="Phone"
onValidate={(value: string) => {
return /\d{10}/.test(value) ? null : "No valid swiss phone number.";
}}
required
/>
</Form>