Developer Programs

Learn

Docs

Large Forms - Wizard

Components > Editing Data > Forms > Large Forms - Wizard
Use this approach to...
Make it easier for users to work with larger forms on desktop

Overview

A form is a collection of labeled inputs that allows the user to input or update a set of data.

Forms typically include the following elements:

  • A set of one or more fields. Each field includes:
    • A text label to identify the field
    • An input element that allows the user to enter/update data for the field
  • Buttons for controlling the form. We display these buttons at the top of the form. These buttons include:
    • Save: Validates the form data and posts the form if no validation errors are found.
    • Cancel: Abandons the form data and returns the user to the previous location in the application.
    • Next: Move to the next screen of fields in a wizard-based form.
    • Back: Move to the previous screen of fields in a wizard-based form.
  • Validation errors identify invalid data in the form.
    • Field-level validation errors are associated with fields in the form. We display each of these directly below the field so the user can see both the data and the validation error together, making it easier for them to understand and correct the error.
    • Form-level validation errors are for the entire form and are not associated with an individual field in the form. We display these above the fields.

In smaller forms that don’t require scrolling, it’s simple for the user to see all of these elements at the same time.

But larger forms — where the user has to scroll the page in order to see all the fields — can present some unique challenges:

  • How can we keep all of these types of form elements easily accessible as the user scrolls down the page?
  • How can we break a large form into smaller pieces to help the user progress through the form?
  • How can we make a large form usable even at mobile size?

This pattern discusses how we can address the challenges presented by larger forms. (Note that forms can also be used for gathering search query inputs, but that specific use case is outside the scope of this pattern.)

It’s up to your product team to make the determination of what is a considered a large form and what isn’t. If a form cannot entirely fit on the screen without scrolling at all supported screen sizes, then it probably should be considered a large form.

Keeping the primary form elements easily accessible

Earlier we mentioned that the three primary elements of forms include fields, validation errors, and buttons. These naturally break down into two categories:

  • Data elements: The fields and field-level validation errors
  • Control elements: The control buttons: Save, Cancel, Next and Back

We display the control elements (buttons) at the top of the form, typically in the display block toolbar.

We display the data elements (fields and any field-level validation errors) below the control elements.

Because the number of data elements can vary a great deal from form to form – anywhere from one field to hundreds of fields – we use vertical scrolling to manage any overflow. I.e. the user scrolls down to access any form fields that are out of view.

As the user scrolls down, the control buttons will scroll out of view. Since these buttons should remain accessible for usability, we use two techniques to keep the control buttons easily accessible:

  • Floating toolbar: The floating toolbar appears at the top of the view after the user scrolls a little down the page and hides again if the user scrolls back up to the top of the view. If we display the control elements (e.g. Save/Cancel buttons) in both the display block toolbar (which can scroll out of view) and in the floating toolbar, then the user will always have easy access to these buttons.
  • Fixed layout: By default, the entire view will scroll vertically when there are more elements than can be seen in the viewport at once. With fixed layout however, we fix 1 or more display blocks in a layout that does not scroll, instead using vertical scrollbars within each display block as needed. This approach keeps the display block toolbar always visible, including the control buttons.

Both of those techniques work well at larger screen sizes, but at mobile size, fixed layout drops into a vertically scrolling layout which allows the control buttons to scroll out of view. So we display the control buttons in both the display block toolbar and in the floating toolbar, even if fixed layout is used. A fixed-layout view can and should include a floating toolbar; this displays at mobile size when the user scrolls down the page.

This view includes a large form. The Save/Cancel buttons display in the display block toolbar at the top, with the fields displayed below that.
Large Form
The Save/Cancel buttons also appear in the floating toolbar. This keeps these buttons easily accessible even as the user scrolls down the page.
Large Form with floating toolbar
Displaying these buttons in the floating toolbar becomes even more important at mobile size, where fewer elements can be seen in the view at a time.
Large Form with floating toolbar on mobile

Another challenge with large forms is making it easy for the user to find and fix validation errors in the form inputs.

This is less of a problem with small forms, since the fields and any validation errors are mostly visible at a glance. But if the form contains a large number of fields, the user sees less fields at a time, which can make it difficult for the user to know where the field-level validation errors are.

And that’s if the user even knows that there are validation errors in the first place. If all fields with validation errors are currently scrolled out of view, the only clue that the user has that validation errors exist is the fact that the Save button is disabled.

This form contains a validation error in a field that has been scrolled out of view. There is no indication for the user that a validation error exists, other than the fact that the Save button is disabled.
Large Form with non-visible validation error

The validation error navigator helps solve these problems.

  • Add the validation error navigator to the display block toolbar and the floating toolbar. It’s visible whenever the form contains any field-level validation errors and hides when there are no validation errors in the form.
  • The validation error navigator specifies the number of validation errors present in the form. Its very presence is a reliable indicator to the user that the form contains validation errors.
  • The validation error navigator includes arrow buttons for navigating between validation errors. Pressing the “next” button scrolls to and sets focus to the next validation error in the form. The “back” button does the same for the previous validation error in the form.

The validation error navigator must appear to the right of all other buttons in the toolbar. The typical order (left to right) will be Save button, Cancel button, then the validation error navigator.

The validation error navigator does not reproduce the list of validation errors, it simply alerts the user to the fact that the form contains validation errors and helps the user navigate to and fix each validation error.

This form contains six validation errors. The validation error navigator displays the number of validation errors in the form, along with buttons that allow the user to navigate to the next and previous validation errors.
Validation error navigator

Breaking form fields into smaller chunks

We can help make large forms more manageable by breaking them into smaller sections.

This can be helpful at any screen size but is especially helpful on smaller screen sizes like mobile, where the user can see far fewer fields on the screen at a time.

The fields in each section of the form should be related, as part of a cohesive sub-group.

For example, you might break a purchase form down into one section with fields for the mailing address, another section with fields for the billing address, another with fields for the payment options, and so forth. It’s important to keep each section large enough to remain cohesive but also small enough to benefit from breaking the form into chunks (and to work well on smaller devices like mobile).

We support two techniques for breaking forms into smaller sections that work well across all screen widths: accordion group boxes and wizards. The wizard approach is described below. See Large Forms - Accordion for details on using accordion group boxes for large forms.

Using a wizard for large forms

A wizard is a multi-step process that uses buttons labeled “< Back” and “Next >” to linearly navigate between steps.

The Back button is disabled on the first step. It uses secondary button styling.

The Next button is titled “Finish” on the last step. It uses primary button styling.

Create one step in the wizard for each of the form’s subsections. Display the current section’s title as the display block title.

The wizard can optionally include step links that (a) show where the user is in the overall process and (b) optionally act as links to other steps in the wizard. We use fixed layout to display the step links in a separate display block to the right of the form. (These step links are hidden at mobile size since they would display below the form on mobile, making their usefulness questionable.)

Large form that uses a wizard to break the form fields into chunks
Large form that uses a wizard

Your application should display error styling on step links for each step that contains a field with a validation error.

We typically expect that a wizard won’t allow the user to move forward to a subsequent step – either by pressing the Next button or by clicking a link in the step links – until they have corrected any validation errors in the current step. In this case, the validation error navigator will only display and navigate errors in the current step, which is still useful.

However, if your wizard does not enforce that behavior, allowing users to skip over validation errors until they press the Finish button to post the form, then as the user navigates between validation errors using the validation error navigator, your application must first move to the appropriate step, scroll the field into view, then set focus to it.

The same form, showing validation errors. Notice that each step link that contains a field with a validation error displays with error styling.
Large form that uses a wizard shown with validation errors

Combining grouping techniques

While we don’t recommend this for most forms, it is possible to combine these two grouping techniques in the same form.

To do this, first start by breaking the form down into a set of high-level sections, then display them as separate steps in a wizard. Then, as needed, break down the fields within each step of the wizard using accordion group boxes within that wizard step.

Ensuring that forms work well at mobile size

The techniques in this pattern work well at all screen sizes, including mobile.

  • The floating toolbar keeps the form’s control buttons always accessible, regardless of how far the user has scrolled down in the form.
  • The validation error navigator helps the user (a) see that the form contains validation errors and (b) navigate between validation errors, without consuming any new space in the view.
  • Breaking the form into sections helps reduce the number of fields the user has to scroll through at a time, which is even more important in the limited viewport that mobile devices offer.
    • Of the two grouping techniques, the wizard approach is a little more mobile-friendly because the user only sees fields for the current step. This limits the user’s navigation to one step at a time using the Back/Next buttons, but this is appropriate behavior for a smaller device.
    • The accordion group box approach also works well on mobile, although the user has to visually skip over any collapsed group boxes in the view.

Development

Web component development

The large form wizard features two display blocks in a horizontal fixed layout:

  • The display block on the left is used for the current wizard step. It includes:
    • A title that specifies the current wizard step
    • Back/Next buttons in the toolbar, for moving backwards and forwards linearly through the wizard steps
    • A validation error navigator in the toolbar, for quickly identifying validation errors in the form and navigating between them
    • Content for the current step in the wizard
  • The display block on the right displays step links for the wizard.
    • These step links can be clickable or non-clickable, depending on your use case. Clicking a clickable step link takes the user directly to that step.
    • The current step link displays in bold.
    • The step link for each step in the wizard that contains a validation error displays in red.

You must also include a floating toolbar for the view that duplicates the Back/Next buttons and the validation error navigator. This ensures that the user always has access to these important navigation buttons and will always be able to tell if the form contains a validation error.

In order to navigate programmatically to the correct input, you’ll need to add an id attribute to the outer div for each form field based on its formControlName attribute. Add a ‘field-’ prefix to the formControlName value to calculate the field’s id value.

Example: Let’s say you have a “Home Phone” field in your form and its formControlName is “HomePhone”. Add an id to the outer div for this field with the value “field-HomePhone”.

Setting the field ID
<input id="field-name" formControlName="name" type="text" class="rui-form-control" />

At larger screen sizes, fixed layout displays the two display blocks side by side.

However, at smaller screen sizes (including mobile), fixed layout displays the two display blocks vertically, with the step links displayed at the bottom of the page. This would force the user to scroll to the bottom of the page just to access the form navigation links, which significantly reduces usability.

Because of this, you should hide the step links display block whenever the screen size drops below the threshold width or height that your product specifies for the fixed layout to drop into vertical scroll mode. The user can still use the Back/Next buttons and the validation error navigator to navigate the form.

Managing Validation Errors using a Wizard

When a form contains validation errors, the validation error navigator displays the number of validation errors in the form, along with buttons for navigating to the previous and next validation errors in the form.

IMPORTANT: The validation error navigator only works with Reactive forms.

Start by connecting the validation error navigator to your Reactive form by binding the jhaForm property to your FormGroup. Define an event handler for the jhaErrorNavigation event. If your form utilizes custom form validators, bind an array of JhaCustomValidationMap elements to the jhaCustomValidationMappings property.

Add the validation error navigator element
<jha-validation-error-navigator [jhaForm]="editForm" (jhaErrorNavigation)="goToFormControl($event)"
    [jhaCustomValidationMappings]="customValidationMaps">
</jha-validation-error-navigator>

When the user presses the previous error or next error buttons in the validation error navigator, it fires the jhaErrorNavigation event, specifying the formControlName for that previous/next field with a validation error in your Reactive form.

Your event handler must:

  • Identify which wizard step this field is contained within.
  • Automatically navigate the user to that step in the wizard.
  • Scroll that field into view.
  • Set focus to that field.

Start by creating a method to determine which step in the wizard the field is contained within. The method will accept one argument, the formControlName value passed to the event handler. Test against each group of names to determine the correct wizard step, then update the wizard step index to display the correct step.

Method that maps a field to a wizard step
public displaySectionForFieldId(formControlName: string) {
    if (formControlName === 'name' ||
        formControlName === 'ssn' ||
        formControlName === 'birthdate' ||
        formControlName === 'lastLogin' ||
        formControlName === 'grossIncome' ||
        formControlName === 'interestRate' ||
        formControlName === 'accountTypes') {
        this.wizardStepIndex = 1;
    } else if (formControlName === 'street' ||
        formControlName === 'city' ||
        formControlName === 'state' ||
        formControlName === 'zip' ||
        formControlName === 'county' ||
        formControlName === 'country' ||
        formControlName === 'addressType') {
        this.wizardStepIndex = 2;
    } else if (formControlName === 'homePhone' ||
        formControlName === 'businessPhone' ||
        formControlName === 'mobilePhone' ||
        formControlName === 'email' ||
        formControlName === 'alerts') {
        this.wizardStepIndex = 3;
    } else if (formControlName === 'comments') {
        this.wizardStepIndex = 4;
    }
    this.WizardSetup();
}

Next create a method to get the correct element by its id. Once identified, test if the element has child input elements. Custom input components such as p-calendar, p-checkbox, etc will need to have focus set on the first child input.

If the element doesn’t have child input elements, it’s a standard html element and focus can be applied to it directly.

Method that finds an element by its id
public focusField(formControlName: string) {
    const nextControl = document.getElementById('field-' + formControlName);
    if (nextControl.getElementsByTagName('input')[0]) {
        // If element has child inputs, set focus on the first child input
        nextControl.getElementsByTagName('input')[0].focus();
    } else {
        // This is a standard input, just set focus
        nextControl.focus();
    }
}

Have the event handler run both methods to change to the correct step and focus the desired input.

Event handler
public goToFormControl(formControlName: string) {
    this.displaySectionForFieldId(formControlName);
    setTimeout(() => { this.focusField(formControlName); }, 100);
}

Next create a public property populated with any custom validators used in the form. Import the JhaCustomValidationMap class, then create an array with the name of the error thrown by the validator, followed by an array of the formControlName values of the fields that need to be addressed in the validation error.

Customer validations property
import { JhaCustomValidationMap } from '@jkhy/responsive-ui-angular/jha-forms';

...

public customValidationMaps: JhaCustomValidationMap[] = [
    new JhaCustomValidationMap('countryAddressTypeMismatch', ['country', 'addressType'])
];

Lastly, you need to notify users of validation errors within steps, even if they are not the current step. To do this, add the rui-step-link-error CSS class to each of the step links that contain an input with a validation error.

Begin by creating a method for each form control to test its validity.

Method that tests field validity
private nameFieldHasError(): boolean {
    return (this.name.invalid && (this.name.dirty || this.name.touched));
}

Create a method for each step in the wizard, to run the test methods of each of the included form components.

Method that tests all fields for validity
public customerInformationSectionHasError(): boolean {
    const hasError: boolean =
    this.nameFieldHasError() ||
    this.ssnFieldHasError() ||
    this.birthdateFieldHasError() ||
    this.lastLoginFieldHasError() ||
    this.grossIncomeFieldHasError() ||
    this.interestRateFieldHasError() ||
    this.accountTypesFieldHasError();

    return hasError;
}

Finally, conditionally add the rui-step-link-error CSS class to steps that return an error.

Assigning error CSS class
<a class="rui-step-link-clickable" (click)="wizardStep(1)"
    [ngClass]="{'rui-step-link-active' : wizardStepIndex == 1, 'rui-step-link-error' : customerInformationSectionHasError()}">Customer Information</a>

We typically expect that a wizard won’t allow the user to move forward to a subsequent step — either by pressing the Next button or by clicking a link in the step links — until they have corrected any validation errors in the current step. In this case, the validation error navigator will only display and navigate errors in the current step, which is still useful.

However, if your wizard does not enforce that behavior, allowing users to skip over validation errors until they press the Finish button to post the form, then as the user navigates between validation errors using the validation error navigator, your application must first move to the appropriate step, then scroll the field into view and set focus to it.


Angular wrapper development

The large form wizard features two display blocks in a horizontal fixed layout:

  • The display block on the left is used for the current wizard step. It includes:
    • A title that specifies the current wizard step
    • Back/Next buttons in the toolbar, for moving backwards and forwards linearly through the wizard steps
    • A validation error navigator in the toolbar, for quickly identifying validation errors in the form and navigating between them
    • Content for the current step in the wizard
  • The display block on the right displays step links for the wizard.
    • These step links can be clickable or non-clickable, depending on your use case. Clicking a clickable step link takes the user directly to that step.
    • The current step link displays in bold.
    • The step link for each step in the wizard that contains a validation error displays in red.

You must also include a floating toolbar for the view that duplicates the Back/Next buttons and the validation error navigator. This ensures that the user always has access to these important navigation buttons and will always be able to tell if the form contains a validation error.

In order to navigate programmatically to the correct input, you’ll need to add an id attribute to the outer div for each form field based on its formControlName attribute. Add a ‘field-’ prefix to the formControlName value to calculate the field’s id value.

Example: Let’s say you have a “Home Phone” field in your form and its formControlName is “HomePhone”. Add an id to the outer div for this field with the value “field-HomePhone”.

Setting the field ID
<input id="field-name" formControlName="name" type="text" class="jha-form-control" />

At larger screen sizes, fixed layout displays the two display blocks side by side.

However, at smaller screen sizes (including mobile), fixed layout displays the two display blocks vertically, with the step links displayed at the bottom of the page. This would force the user to scroll to the bottom of the page just to access the form navigation links, which significantly reduces usability.

Because of this, you should hide the step links display block whenever the screen size drops below the threshold width or height that your product specifies for the fixed layout to drop into vertical scroll mode. The user can still use the Back/Next buttons and the validation error navigator to navigate the form.

Managing Validation Errors using a Wizard

When a form contains validation errors, the validation error navigator displays the number of validation errors in the form, along with buttons for navigating to the previous and next validation errors in the form.

IMPORTANT: The validation error navigator only works with Reactive forms.

Start by connecting the validation error navigator to your Reactive form by binding the jhaForm property to your FormGroup. Define an event handler for the jhaErrorNavigation event. If your form utilizes custom form validators, bind an array of JhaCustomValidationMap elements to the jhaCustomValidationMappings property.

Add the validation error navigator element
<jha-validation-error-navigator [jhaForm]="editForm" (jhaErrorNavigation)="goToFormControl($event)"
    [jhaCustomValidationMappings]="customValidationMaps">
 </jha-validation-error-navigator>

When the user presses the previous error or next error buttons in the validation error navigator, it fires the jhaErrorNavigation event, specifying the formControlName for that previous/next field with a validation error in your Reactive form.

Your event handler must:

  • Identify which wizard step this field is contained within.
  • Automatically navigate the user to that step in the wizard.
  • Scroll that field into view.
  • Set focus to that field.

Start by creating a method to determine which step in the wizard the field is contained within. The method will accept one argument, the formControlName value passed to the event handler. Test against each group of names to determine the correct wizard step, then update the wizard step index to display the correct step.

Method that maps a field to a wizard step
public displaySectionForFieldId(formControlName: string) {
    if (formControlName === 'name' ||
        formControlName === 'ssn' ||
        formControlName === 'birthdate' ||
        formControlName === 'lastLogin' ||
        formControlName === 'grossIncome' ||
        formControlName === 'interestRate' ||
        formControlName === 'accountTypes') {
        this.wizardStepIndex = 1;
    } else if (formControlName === 'street' ||
        formControlName === 'city' ||
        formControlName === 'state' ||
        formControlName === 'zip' ||
        formControlName === 'county' ||
        formControlName === 'country' ||
        formControlName === 'addressType') {
        this.wizardStepIndex = 2;
    } else if (formControlName === 'homePhone' ||
        formControlName === 'businessPhone' ||
        formControlName === 'mobilePhone' ||
        formControlName === 'email' ||
        formControlName === 'alerts') {
        this.wizardStepIndex = 3;
    } else if (formControlName === 'comments') {
        this.wizardStepIndex = 4;
    }
    this.WizardSetup();
}

Next create a method to get the correct element by its id. Once identified, test if the element has child input elements. Custom input components such as p-calendar, p-checkbox, etc will need to have focus set on the first child input.

If the element doesn’t have child input elements, it’s a standard html element and focus can be applied to it directly.

Method that finds an element by its id
public focusField(formControlName: string) {
    const nextControl = document.getElementById('field-' + formControlName);
    if (nextControl.getElementsByTagName('input')[0]) {
        // If element has child inputs, set focus on the first child input
        nextControl.getElementsByTagName('input')[0].focus();
    } else {
        // This is a standard input, just set focus
        nextControl.focus();
    }
}

Have the event handler run both methods to change to the correct step and focus the desired input.

Event handler
public goToFormControl(formControlName: string) {
    this.displaySectionForFieldId(formControlName);
    setTimeout(() => { this.focusField(formControlName); }, 100);
}

Next create a public property populated with any custom validators used in the form. Import the JhaCustomValidationMap class, then create an array with the name of the error thrown by the validator, followed by an array of the formControlName values of the fields that need to be addressed in the validation error.

Customer validations property
import { JhaCustomValidationMap } from '@jkhy/responsive-ui-angular/jha-forms';

...

public customValidationMaps: JhaCustomValidationMap[] = [
    new JhaCustomValidationMap('countryAddressTypeMismatch', ['country', 'addressType'])
];

Lastly, you need to notify users of validation errors within steps, even if they are not the current step. To do this, add the jha-step-link-error CSS class to each of the step links that contain an input with a validation error.

Begin by creating a method for each form component to test its validity.

Method that tests field validity
private nameFieldHasError(): boolean {
    return (this.name.invalid && (this.name.dirty || this.name.touched));
}

Create a method for each step in the wizard, to run the test methods of each of the included form components.

Method that tests all fields for validity
public customerInformationSectionHasError(): boolean {
    const hasError: boolean =
    this.nameFieldHasError() ||
    this.ssnFieldHasError() ||
    this.birthdateFieldHasError() ||
    this.lastLoginFieldHasError() ||
    this.grossIncomeFieldHasError() ||
    this.interestRateFieldHasError() ||
    this.accountTypesFieldHasError();

    return hasError;
}

Finally, conditionally add the jha-step-link-error CSS class to steps that return an error.

Assigning error CSS class
<a class="jha-step-link-clickable" (click)="wizardStep(1)"
    [ngClass]="{'jha-step-link-active' : wizardStepIndex == 1, 'jha-step-link-error' : customerInformationSectionHasError()}">Customer Information</a>

We typically expect that a wizard won’t allow the user to move forward to a subsequent step — either by pressing the Next button or by clicking a link in the step links — until they have corrected any validation errors in the current step. In this case, the validation error navigator will only display and navigate errors in the current step, which is still useful.

However, if your wizard does not enforce that behavior, allowing users to skip over validation errors until they press the Finish button to post the form, then as the user navigates between validation errors using the validation error navigator, your application must first move to the appropriate step, then scroll the field into view and set focus to it.


Design

Figma design

Figma design info
You can find this component in the Components - Forms page in the Figma UI Kit.

A large form that uses a wizard is designed just like any other wizard: content in the left display block, step links in the right display block.

See Wizard for details on designing a wizard.


Adobe XD design

Adobe XD design info
You can find this component in these artboards in the Adobe XD design samples:
  • Sample App - Large Forms: Wizard

A large form that uses a wizard is designed just like any other wizard: content in the left display block, step links in the right display block.

See Wizard for details on designing a wizard.


Support options
Have questions on this topic?
Join the Responsive UI team in Microsoft Teams to connect with the community.
See something in this page that needs to change?
Send us feedback on this page.
Last updated Tue Feb 27 2024