The Basics

What it is

FormField bundles all the functionality needed to make the inputs in a form intuitive and accessible. This includes a label, hint text, and room for error messaging. It is designed for use inside the Form component.

How it works

  • The user clicks or focuses on an input wrapped in FormField, which makes it active.
  • While the field is active, the user can add and edit data in it.
  • If the field has validation or required field checking (not built in; must be implemented by your team) and the user-entered data doesn’t match the validation rules, an error appears next to or below the field on submit.

When to use

  • To enable responsive, user-friendly features for Forge inputs
  • When designing forms that should follow the typical Forge layout and styling
  • To build a form quickly without having to spend time customizing layout and styles

When not to use

  • For a standalone form input that doesn’t need any of the features enabled by FormField

What to use instead

MultiField

Use MultiField to group related inputs on the same line.

How to use

FormField is designed for use inside the Form component. Use FormField to display a single input field. To display a group of related fields, use MultiField instead.

Input validation

FormField (and its container, Form) do not have built-in input validation (code that checks user-entered data against a set of expectations and flags any errors) or required field checking. You can set a FormField to be required, but your team must build the code that confirms whether data has been entered in it.

FormField elements and options

FormField includes these elements:

  • Input: This can be any Forge form input (like Checkbox, DateInput, or RadioGroup) or a custom-built component. It defaults to Input if no component has been specified.
  • Label (required): Text that introduces the input and indicates what users should enter.
  • Hint text (optional): Short instructional text displayed below the input that helps users enter data correctly (like “PDF files only”).
  • Error message (optional): An input validation warning message that can be shown if there are problems with the user-entered data. Depending on the space available, this is displayed to the right of the input or below it. It can be set to always appear below the input.

These options can be set for FormField:

  • Required: Setting a FormField as required means that users must enter data in its input. The visual treatments for required fields are set for the entire parent Form. See Form for the required field styles and help choosing the best one for your use case.
  • Disabled: Setting a FormField as disabled allows you to display the input but prevent users from interacting with it. This is useful for temporarily deactivating it until a certain condition is met. For example, you may want users to know that there is a field for the appointment date, but you don’t want them to enter data in it until they select a clinic location.

These design aspects are set for the whole Form, but you can override them and set them for individual FormFields (to create complex form layouts):

  • Layout: The level of visual density of the Form. This can be set to medium (default), compact, or super-compact.
  • Label placement: Whether the label appears to the left of the input (and switches to above the input when the viewport shrinks) or it always appears above the input.
  • Label space width: The amount of white space taken up by the label when it’s displayed to the left of the input (based on Forge’s Grid layout framework).

See the UX guidance for Form for full details about these design aspects.

Style

Design details

The height of this component and the vertical space around it vary according to the form layout (i.e., large, medium, compact, super-compact). See Form for details.

Placement and hierarchy

No additional information for this component.

Content

Use sentence case for the label ("Reason for visit”, not “Reason for Visit”) and the other text elements of FormField (hint text, error message, and any placeholder text inside the input).

Label

FormField requires a label for its input. Label text should be as short as possible and describe the data users should enter. Be aware that one of Form’s required field styles (the “Bar with “- Required” label” option) adds the text “ - Required” to the end of any required field’s label.

Don’t end your label text with colons (“First name”, not “First name:”).

Labels can have formatting, like bold text. Use this carefully because it can interfere with users being able to quickly scan through form fields. Don't design labels with links or other clickable features inside them. This can interfere with native browser handling for form labels.

Hint text and placeholder text

Hint text (optional) is displayed below the input field. Use this for very short instructions about the input. For example:

  • “Maximum file size is 25 MB”
  • “Numbers and dashes only, no letters”

Placeholder text (optional) is displayed inside the input field. It shouldn’t duplicate the label text. Use this to help guide the user about entering data correctly or using the input effectively. For example:

  • For an Input, the text can show an example of the expected data: “e.g., yourname@domain.com”
  • For a Multiselect, the text can give instructions on how to use it: “- Type or select patient names -”

Placeholder text often disappears when the user interacts with the field. This can be an accessibility problem for users who are multitasking, tabbing quickly through a form, or experiencing memory issues. For this reason, don’t use placeholder text for important instructions. Instead, use hint text, which remains visible.

Info icon for additional information

If you want to include helpful information about the field, we recommend adding an Info icon with a Tooltip at the end of the label text. This is a common pattern for providing details about the workflow, like why a field is required or where the data appears in athenaOne. Don’t attach the Tooltip to the label text itself; users won’t know to hover over it. The Info icon provides this visual cue.

Unlike hint text (which is always visible), Tooltip text is visible only when the user hovers over the Info icon. Don’t use the Tooltip for details that users must know to complete the workflow.

The Info icon and Tooltip are not included in FormField and must be added by your team.

Error message

FormField’s error message should be short, clear, specific, and easy to understand. The message should describe the problem. For example:

  • “Patient name is required”
  • “Email address should be in this format: name@domain.com”
  • “This file is too big to upload (maximum size is 25 MB)”

For help writing error messages, see Content.

Demos

Form Layout Share

Basic Form Share

Form Validation Share

Required Variations Share

Coding

Developer tips

FormField is meant to be used as a child of Form.

Input validation and required fields

FormField does not include built-in validation or checking for required field values. Teams must implement this themselves. See the Form Validation demo for example code.

Use the required prop to set a FormField as required. This allows any input validation code to handle it. The styling for required form fields is set at the Form level. See Form for details.

Use the error prop to set an error message flagged by input validation.

Labels

FormField’s label accepts a React node, so you can put custom content in it (like formatted text).

Don’t put links or other clickable features inside the label. This interferes with native browser behaviors for form labels.

It’s possible to put display-only elements like icons and Avatar inside the label, but it should be done rarely because it can create visual clutter and make it harder for users to scan all the labels. One important exception to this is adding an Info icon with a Tooltip at the end of the label to include explanatory information about the input.

Passing props to FormField children

Any props that aren't listed in the FormField API are passed through to the wrapped input, except for className. This allows you to set aspects of the underlying input at the FormField level, whether the input is a Forge form input or something custom built by your team.

Complex input components

It's a common problem where an application wants the label + content look and feel of a FormField, but the content is more complex what Forge ships. In some cases, Multifield can fill that gap, but that component may not be suitable either.

One limitation of FormField, is that it expects the component passed to the inputAs to support the props documented in FormFieldTypes and to POST its field data on form submission. If you want to use a custom component as the inputAs slot, then you either need to make this custom component conform to this API or use FormFieldLayout instead. The advantage to using FormFieldLayout over FormField, is that FormFieldLayout's inputSlot prop accepts arbitrary JSX rather than a component definition. For example, if you want a Button indented with the rest of your form content, you can do this:

<FormFieldLayout
inputSlot={<Button text="Click me" onClick={handleButtonClick} />}
labelSlot={<Label text="Button Label" />}
/>

The props for FormFieldLayout are also documented on this page, but it may be worthwhile inspecting the FormField and FormFieldLayout implementations for more complex features.

Typescript

Typing FormField accurately required making FormField a generic component whose generic type parameter represents the props supported by the inputAs component. This generic parameter will auto-infer correctly most of the time, but there are situations where this auto-inference cannot be done. For example:

  • The types of your props are not compatible with each other, and you need to debug which props are not conforming to what you intended.
  • You're supplying a callback function as a prop and don't want to explicitly type every positional parameter.
  • Using another generic component in the inputAs prop, such as Select. In short, the following two lines are equivalent:
<FormField id="date-input" labelText="Date Input" inputAs={DateInput} />
<FormField<DateInputProps> id="date-input" labelText="Date Input" inputAs={DateInput} />

Repository

Implementation links

FormField directory in Bitbucket

FormFieldLayout directory in Bitbucket

Implementation details

It is strongly recommended to familiarize yourself with the Forge source code. While this documentation is a best effort to document the intent and usage of a component, sometimes some features only become clear when looking at the source code. Also, looking at Forge's source code may help identify and fix bugs in either your application or Forge itself.

Storybook files

Forge maintains at least one storybook file per component. While the primary audience for these files is typically the Forge team, these storybook files may cover usages of the component not covered by a demo. The storybook for the latest version of forge can be found at go/forge-storybook.

Testing library

Forge strongly encourages using testing-library to write tests for your application.

"The more your tests resemble the way your software is used, the more confidence they can give you."

If you're having trouble testing a Forge component using testing-library, it would be a good idea to see how Forge tests its own components. For the most part, Forge tries to use screen.getByRole as much as it can, as that API provides the best feedback on a11y compliance. Forge discourages the use of document.querySelector and screen.getByTestId as both APIs encourage using implementation details to test your component, and discourage adding roles to your component.

With that being said, many of Forge's components were not built with accessability in mind. These components do break the recommendations listed above.

Import statements

In Nimbus applications

athenaOne serves the Forge bundle independently from your application's bundle. Importing Forge components directly from '@athena/forge' takes advantage of this feature.

import { FormField, FormFieldLayout } from '@athena/forge'

In standalone applications

Importing components using the exact path to the module takes advantage of webpack's tree shaking feature. Webpack will include only that module and its dependencies.

import FormField from '@athena/forge/FormField';
import FormFieldLayout from '@athena/forge/FormFieldLayout';

To use this import guidance, Typescript applications must use typescript >= 4.7.3, and should add this setting to their tsconfig.json file:

{
"compilerOptions": {
"moduleResolution": "Node16",
}
}

If this setting doesn't work for your application, use this import statement instead:

import FormField from '@athena/forge/dist/FormField';
import FormFieldLayout from '@athena/forge/dist/FormFieldLayout';

Props