Form Field

Mobile Support: Partial
Jump to Props

Maintain consistency in form structures

Import

import { FormField } from "@vitality-ds/components";

Usage

A field represents a section of a form that combines an input, label and additional text. Most forms will be made up of a collection of FormField components.

In its most basic use:

<FormField
  type="text"
  label="Work email address"
  id="email"
  value={null} // eg. this.state.text;
  inputProps={{ size: 30 }}
  onChange={(e) => console.log(e.target.value)} // eg. e => setText(e.target.value);
/>

Contents

All FormField components are comprised of the below elements:

  1. Labels: Inform users what the corresponding input fields mean.
  2. Input 'type': Enable users to provide information. Information can be entered through a variety of different input fields ranging from text fields, checkboxes, and many other types.
  3. Helper message: Communicate useful information on how to fill out a field. Helper text will become 'critical' (red) in colour if there is an error. Helper text is optional.
  4. Placeholder text: Hints at the type of data that should be entered into a field including its formatting. Placeholder text is optional. (Specific to TextInputs)
  5. Name: The name is a prop which is passed to all input fields. Name is a descriptive title
Anatomy of the FormField component in Vitality

FormField Labels

A Label helps the user to understand what information is being requested of them. Use labels to provide context to an input field describing the purpose of that field.

  1. Use verbs sparingly. Labels have inferred purpose and instruction; long-winded labels are not required and can cause visual clutter. For example, providing a text input with a label saying "First name", has the same effect as having a label saying "Insert your first name here". However, saying "First name" is less visually taxing.

  2. Don't add a colon (":") at the end of a field label to imply that the label text applies to the field it accompanies. The component's design already communicates the relationship between the label and the input field and adds unnecessary characters.

  3. Use sentence case for all label text with a capital letter for the first word and lowercase for subsequent comments.

Below is an example of a helpful and a flawed label for a simple First name FormField.

() => {
  const data = {
    type: "text",
    id: "first_name",
    onChange: () => {},
    inputProps: {
      name: "firstName",
    },
  };

  return (
    <Stack>
      <FormField
        {...data}
        inputProps={{ placeholder: "This has a good label" }}
        label="First name"
      />
      <FormField
        {...data}
        inputProps={{ placeholder: "This has a bad label" }}
        label="Insert First Name Here:"
      />
    </Stack>
  );
};

FormField Required

The required prop for FormField is a boolean attribute that adds a symbol to the label, indicating the field as mandatory.

This is simply a visual indication. No validation logic is performed when required. Validation checks should be done externally and communicated via the hasError and helperMessage props.

() => {
  const data = {
    type: "text",
    label: "Patient name",
    id: "patient_name",
    onChange: () => {},
    inputProps: {
      name: "emailAddress",
    },
  };

  return <FormField required {...data} />;
};

FormField Types

A FormField can display different input types.

  1. Text - Used for single lines of text that the user should have control over such as:
    1. Names
    2. Numbers
  2. Checkbox - Used to input set values, where more than one item can be selected such as:
    1. Contact availability (Morning, Afternoon, Evening, Night)
    2. Allergies (Nuts, Gluten, Dairy, Meat)
  3. RadioButtons - Used to input set values, where only one item from a mutually exclusive list can be selected such as:
    1. Preferred Method of contact (Email, Phone, Text)
    2. Prescribing Method (Print, E-Prescribing token, Direct to pharmacy)
  4. Password - Used to input a password:
    1. Login
  5. Currency - Used to process currency values:
    1. Invoices
    2. Payment values
  6. Duration - Used to display human readable durations:
    1. Appointment Durations
  7. Textarea - Used for multi-line text inputs such as:
    1. Patient notes
    2. Diary entries
  8. Select - Used to input set values:
    1. Country / State
    2. Or allow users to add their own from a list of premade suggestions with the creatableSelect type
    3. Use the asyncSelect type to make asynchronous calls to populate the select menu
  9. DatePicker - Used to input date values:
    1. Appointment/procedures
    2. Invoices
    3. Pregnancy records
  10. DateRangePicker - Used to input date values:
    1. Search Filtering by time period
    2. Setting the dates for events that span multiple days
  11. TimePicker - used to select a time from a list, or let users add their own time
    1. Selecting appointment times
    2. Generating invoices
  12. SearchInput - Used as a text input where the onChange function handles asynchronous functions.
    1. Searching through data tables
    2. Validation of name, BSB or other data matching requests

Text type

Basic text input. Read more about the Text Input component here and all the valid props that can be passed.

() => {
  const data = {
    type: "text",
    label: "Patient name",
    id: "patient_name",
    onChange: () => {},
    inputProps: {
      name: "emailAddress",
    },
  };

  return <FormField {...data} />;
};

Checkbox type

Use this type for multiple checkboxes. For single checkboxes, do not use the FormField component as it will require you to add 2 labels for a single option, use the Checkbox component instead.

Each item in the options array requires its own id in order to make the checkbox label clickable.

Read more about the Checkbox component here and all the valid props that can be passed.

() => {
  const [checked, setChecked] = React.useState(false);

  const data = {
    type: "checkbox",
    label: "Contact availability",
    id: "contactAvailability",
    value: ["contactAvailability"],
    onChange: (checkedItems) => console.log(checkedItems),
    inputProps: {
      name: "contactAvailability",
      options: [
        {
          label: "Morning",
          value: "morning",
          id: "morning",
        },
        {
          label: "Afternoon",
          value: "afternoon",
          id: "afternoon",
        },
        {
          label: "Evening",
          value: "evening",
          id: "evening",
        },
        {
          label: "Night",
          value: "night",
          id: "night",
        },
      ],
    },
  };

  return (
    <form>
      <Stack spacing="xl">
        <FormField
          {...data} // For a list of options using FormField
        />
        <Checkbox // Standalone option in the context of a form
          onChange={() => setChecked(!checked)}
          checked={checked}
          value="remember"
          name="remember"
          id="remember"
          label="Remember me"
        />
      </Stack>
    </form>
  );
};

Radio type

Use this type for a group of items where the user has to select only one option. Read more about the Radio Buttons component here and all the valid props that can be passed.

() => {
  const data = {
    type: "radioButtons",
    label: "Preferred contact method",
    id: "contactMethod",
    onChange: (newValue) => console.log(newValue),
    inputProps: {
      name: "contactMethod",
      options: [
        {
          label: "SMS",
          value: "sms",
          id: "sms",
        },
        {
          label: "Email",
          value: "email",
          id: "email",
        },
        {
          label: "Phone call",
          value: "phoneCall",
          id: "phoneCall",
        },
      ],
    },
  };
  return <FormField {...data} />;
};

Password type

Use this type when requesting the user enter a password.

() => {
  const [password, setPassword] = React.useState("Pa$$word!1");
  const data = {
    type: "password",
    label: "Password",
    id: "password",
    helperMessage: "Minimum of 8 characters",
    value: password,
    onChange: (e) => setPassword(e.target.value),
    inputProps: {
      name: "password",
    },
  };
  return <FormField {...data} />;
};

Currency type

Use this type when requesting the user to enter currency values.

() => {
  const [cost, setCost] = React.useState("");
  const data = {
    type: "currency",
    label: "Currency",
    id: "currency",
    value: cost,
    onChange: (e) => setCost(e.target.value),
    inputProps: {
      name: "currency",
    },
  };
  return <FormField {...data} />;
};

Duration type

Custom end adornment to display human-readable durations, use this when expecting duration information (converts from minutes). Read more about the DurationInput component here

() => {
  const [appointmentDuration, setAppointmentDuration] = React.useState("");
  const fieldData = {
    type: "duration",
    label: "Appointment duration (minutes)",
    id: "appointmentDuration",
    onChange: (event) => setAppointmentDuration(event.target.value),
    value: appointmentDuration,
  };

  return <FormField {...fieldData} />;
};

Textarea type

Use this type for multi-line text inputs like comments or notes where many lines of text are expected to be input. Read more about the Textarea component here.

() => {
  const [value, setValue] = React.useState(
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
  );
  const data = {
    type: "textarea",
    label: "Patient notes",
    id: "patientNotes",
    onChange: (event) => setValue(event.target.value),
    value: value,
    inputProps: {
      name: "patientNotes",
    },
  };

  return <FormField {...data} />;
};

Select type

Select is a type of input that opens a list of options. If there are more than 5 choices, select would be ideal instead of checkbox or radio as the dropdown list is a better use of space. Select supports single or multiple values being returned from the list, see Select for a full list of props. There are also a number of select variations, like Createable Select, Async Select and Search Select. To use these variations, simply pass their name to the type prop as camelCase (eg. createableSelect, searchSelect etc.)

() => {
  const [value, setValue] = React.useState({});
  const data = {
    type: "select",
    label: "State",
    id: "state",
    onChange: (value) => setValue(value),
    inputProps: {
      name: "state",
      options: [
        { label: "QLD", value: "qld" },
        { label: "NSW", value: "nsw" },
        { label: "TAS", value: "tas" },
        { label: "VIC", value: "vic" },
        { label: "WA", value: "wa" },
        { label: "NT", value: "nt" },
        { label: "ACT", value: "act" },
        { label: "SA", value: "sa" },
      ],
    },
  };
  return <FormField {...data} />;
};

Select with Chips

You can configure a select Form Field to accept multiple values via the Select props passed through inputProps:

() => {
  const [value, setValue] = React.useState([
    { label: "Addaven injection, 20 x 10 mL ampoules", value: "add" },
    {
      label: "Faricimab 6mg/0.05mL - Intravitreal",
      value: "far",
    },
  ]);
  const data = {
    type: "select",
    label: "Selected medications",
    id: "state",
    onChange: (value) => setValue(value),
    value: value,
    inputProps: {
      placeholder: "Search for medications...",
      isMulti: true,
      name: "state",
      options: [{ label: "QLD", value: "qld" }],
    },
  };
  return <FormField {...data} />;
};
() => {
  const data = {
    type: "select",
    label: "Assign to",
    id: "recipient",
    required: true,
    value: [
      {
        value: "Deirdre_Johnson",
        label: "Deirdre Johnson",
        iconProps: { icon: Practitioner },
      },
      {
        value: "John_Drehaus",
        label: "John Drehaus",
        iconProps: { icon: AccountHolder },
      },
    ],
    onChange: (value) => setValue(value),
    inputProps: {
      placeholder: "Search for assignees...",
      isMulti: true,
      name: "name",
      options: [
        {
          label: "Deirdre Johnson",
          value: "djohnson",
          metaLabelProps: {
            label: "Email",
          },
          iconProps: {
            icon: Patient,
          },
        },
      ],
    },
  };
  return (
    <DocsBox css={{ width: "100%" }}>
      <Stack align="stretch">
        <Typography variant="sectionTitle">Add a To Do</Typography>
        <FormField {...data} />
        <FormField
          type="textarea"
          label="Note"
          inputProps={{ placeholder: "Add here..." }}
        />
      </Stack>
    </DocsBox>
  );
};

DatePicker type

The datePicker input element is a form field that allows users to select a specific date from a calendar-style interface. This input type is often used for fields such as appointment or procedure dates where a specific date must be selected. DatePicker returns the value as a string in the format "dd/mm/yyyy" see DatePicker for a full list of props options.

() => {
  const [date, setDate] = React.useState("");

  const data = {
    type: "datePicker",
    label: "Appointment date",
    id: "appointment_date",
    onChange: (value) => setDate(value),
    value: date,
    inputProps: {
      name: "appointment_date",
    },
  };

  return <FormField {...data} />;
};

DateRangePicker type

The dateRangePicker input element is a form field that allows users to select a date range from a calendar-style interface. This input type is often used for search filtering where the options should be limited to a date range. DateRangePicker returns the value as an array of strings in the format ["dd/mm/yyyy", "dd/mm/yyyy"] see DateRangePicker for a full list of props options.

() => {
  const [dateRange, setDateRange] = React.useState([]);

  const data = {
    type: "dateRangePicker",
    label: "Filter by date",
    id: "filter_date_range",
    onChange: (value) => setDateRange(value),
    value: dateRange,
    inputProps: {
      name: "filter_date_range",
    },
  };

  return <FormField {...data} />;
};

TimePicker type

The timePicker input element is a form field that allows users to select a specific time from a list or add their own time in the selection. This input type is often used for selecting appointment times. TimePicker returns an object with a label and a value both of which are the time, either selected or what the user inputted. see TimePicker for a full list of props.

() => {
  const [time, setTime] = React.useState();

  const data = {
    type: "timePicker",
    label: "Appointment time",
    id: "appointment_time",
    onChange: (newValue) => setTime(newValue),
    value: time,
    inputProps: {
      name: "appointment_time",
    },
  };

  return <FormField {...data} />;
};

SearchInput type

The searchInput is a TextInput with preset adornments and can handle a loading state, it is used to handle text inputs where the onChange function performs an asynchronous function and can display a loading state.

() => {
  const [searchRequest, setSearchRequest] = React.useState("");

  const data = {
    type: "searchInput",
    label: "Search",
    id: "search_input",
    onChange: (event) => setSearchRequest(event.target.value),
    value: searchRequest,
    inputProps: {
      name: "search_input",
      isLoading: searchRequest && true,
    },
  };

  return <FormField {...data} />;
};

inputProps

Each <FormField /> type has a list of inputProps that can or should be added. The documentation for each input type covers these props. However, there are some critical things to consider when developing with <FormField> instead of developing with those Input components directly.

Firstly, object vs. attributes notation. When developing with the inputs, for example, <TextInput /> directly, you can apply the various props as attributes to the JSX element such as <TextInput disabled> where the boolean disabled is implied as true. inputProps handles the props of the individual checkbox/radio/text etc. as an object. In the example of disabled you would need to right inputProps={{disabled: true}} These two snippets will render the same text Inputs:

<Stack>
  <FormField
    type="text"
    hasError
    inputProps={{ name: "name", id: "id", value: null }}
  />
  <TextInput name="name" id="id" hasError value={null} />
</Stack>

Secondly, Inputs that utilise a list of items, specifically a list of related <Checkbox />'s or a group of <RadioButtons />. inputProps can be passed an options attribute which will take the form of an array of objects for example <FormField type="checkbox" inputProps={ options: [ {...option1}, {...option2} ] }>

Handling Data

The FormField component is intended to be a controlled component. Essentially this means its state is handled from the "outside" world.

On a FormField the three attributes which handle this data management are the value of the field, the onChange function of the field and the name attribute.

value

value represents the field value. In cases of singular input fields, this value would generally be the same as that input. In cases where inputs comprise a list (for example, type="checkbox"), the field's value would store an array of all the checked items. This means if the state is initialised prior you can prepopulate fields.

() => {
  const [text, setText] = React.useState("Joe");

  return (
    <Stack>
      <FormField
        // The value has been set from that external state
        value={text}
        onChange={(e) => console.log(e.target.value)}
        id="firstName"
        label="First name"
      />
    </Stack>
  );
};

onChange

The function passed to onChange is triggered by the appropriate change event for that input. This could be a click in the case of checkbox or typing for text input. Read more about change events on MDN.

Typically the onChange handler should update the outside state, for example:

() => {
  const [text, setText] = React.useState("");

  return (
    <Stack>
      Current Value: {text}
      <FormField
        value={text}
        // Text input returns its value via the `event` object
        onChange={(e) => setText(e.target.value)}
        id="firstName"
        label="First name"
        inputProps={{ placeholder: "Type Something" }}
      />
    </Stack>
  );
};

The onChange handler for the <RadioButtons> component does not return an event object – it returns a string equal to the value of the radio button that was selected. This behaviour is slightly different to other input types due to how its internal state is managed. Usually, a FormField using type="radioButtons" should store this value in the state and update it with the new value as the onChange is triggered.

name

The name of each input provides a descriptive title that pairs with the input value, similar to a key/value pair in an object. Having an effective name to match the associated data will assist in the management and handling of that data.

For more information about the name attribute check out MDN

Name should be passed into inputProps.

() => {
  const [formData, setFormData] = React.useState({
    firstName: "Joe",
    lastName: "Bloggs",
  });

  function changeHandler(event) {
    setFormData({
      ...formData,
      [event.target.name]: event.target.value,
    });
  }

  return (
    <Stack>
      FormData: {formData.firstName} {formData.lastName}
      <FormField
        value={formData.firstName}
        onChange={changeHandler}
        id="firstName"
        label="First name"
        inputProps={{ name: "firstName" }}
      />
      <FormField
        value={formData.lastName}
        onChange={changeHandler}
        id="lastName"
        label="Last name"
        inputProps={{ name: "lastName" }}
      />
    </Stack>
  );
};

Helper Message

Use helper messages to communicate useful information to the user about this FormField. In some fields, the content can provide additional context around what kind of data the field expects – and in other cases it can be used to hint at the format of data required.

When a FormField is invalid, reflected by the hasError prop, the helper message will have red colouring to indicate something is wrong.

<FormField
  label="Work email address"
  type="text"
  id="email"
  value="patti@gmail"
  onChange={() => {}}
  helperMessage="The system requires your work email address in order to connect your account with the organisation. Please use the format of you@example.com"
/>

hasError

Like other field data, validation logic is handled from the outside state and passed in to the component. Passing the hasError prop will apply 'critical' styling to the component and the helper message, granted there is a helper message provided, and the type of input has an error state (see each individual component to check if hasError is applicable).

For instant feedback, you can pass event handlers such as onFocus or onBlur to the input via inputProps.

In reality, you would probably handle validation with much more logic than this, however this is intended to be an example of how to interact with the FormField component.

() => {
  const [hpio, setHpio] = React.useState("");
  const isValid = hpio.trim().length < 9;

  const fieldData = {
    type: "text",
    label: "Healthcare provider identifier - Organisation - (HPI-O)",
    id: "hpio",
    value: hpio,
    onChange: (e) => setHpio(e.target.value),
    helperMessage: "HPI-O is 8 characters long.",
    inputProps: {
      name: "hpio",
      "aria-invalid": !isValid,
    },
    hasError: !isValid,
  };

  return <FormField {...fieldData} />;
};

Helper icon

In case a field needs additional context or explanation beyond what can be conveyed by a brief label or helper message, the helper icon can be an effective solution. By providing a string to the infoTooltipContent prop, a small information icon will appear to the right of the field label, allowing you to include extensive context without affecting the sleek flow of Vitality FormFields. This feature provides ample space for a couple of additional sentences of text, making it easier to convey necessary information to users, especially considering that the label and helper message should be as short as possible.

() => {
  const [hpio, setHpio] = React.useState("");
  const isValid = hpio.trim().length < 9;

  const fieldData = {
    infoTooltipContent:
      "HPI-O is a number for matching records to patients. The Healthcare Identifiers Service uses it to identify people, providers, and organizations.",
    type: "text",
    label: "Healthcare provider identifier (HPI-O)",
    id: "hpio",
    value: hpio,
    onChange: (e) => setHpio(e.target.value),
    helperMessage: "HPI-O is 8 characters long.",
    inputProps: {
      name: "hpio",
      "aria-invalid": !isValid,
    },
    hasError: !isValid,
  };

  return <FormField {...fieldData} />;
};

Disabling a field

Disabling a field will only affect the input – so pass disabled: true via inputProps. As some FormField types take different inputProps, please refer to each individual example field type for where to pass the disabled prop.

<FormField
  type="text"
  label="Work email address"
  id="email"
  value={null} // eg. this.state.text;
  inputProps={{ disabled: true }}
  onChange={(e) => console.log(e.target.value)} // eg. e => setText(e.target.value);
/>

Accessibility

  • Don't forget to include aria-invalid in inputProps and set is as true/false

Props

hasError

Description

Indicate if the input has an error. Critical coloured styling will apply to the helper message and pass to the input if true.

Type

boolean

helperMessage

Description

Content to display a helper message.

Type

ReactNode

idRequired

Description

Unique ID of the field - applied to the field's label element. For CheckboxLists`, this ID is used for the `name field so that all checkboxes are submitted together.

Type

string

infoTooltipContent

Description

Provides an icon next to the label with a tooltip message that provides additional context to the user. Text that is too long or not relevant to the format that a helper or error message provides.

Type

string

inputProps

Description

Props passed to the input component(s) rendered based on the type property. id` is omitted because it's passed from the parent FormField `id prop.

Type

Omit<MakeOptional<InputProps<T>, "onChange" | "value">, "id">

labelRequired

Description

The text label to be displayed above the input field.

Type

string

onChangeRequired

Description

Callback that is passed to the input` component rendered based on the `type prop.

Type

((newValue: (BaseOption & Omit<ChipProps, "chipCheckboxProps"> & { chipCheckboxProps?: ChipCheckboxType & { onClick?: (thisItem: BaseOption) => void; }; })[]) => void) | ((newValue: BaseOption) => void) | (ChangeEventHandler<HTMLInputElement> & ((e: ChangeEvent<HTMLInputElement>) => void)) | (ChangeEventHandler<HTMLTextAreaElement> & ((event: HTMLInputElement) => void)) | ((CheckedItems: any) => string[]) | ((newValue: string) => void) | ((value: ReturnObjectType) => void) | ((event?: OnChangeReturnType) => string | void) | ((event: OnChangeDateRangeReturnType) => void)

required

Description

Indicate whether or not the field's label should indicate a required field. Marked with an asterisk.

Type

boolean

typeRequired

Description

Indicate which type of input(s) to display in the field.

Type

"text" | "checkbox" | "radioButtons" | "textarea" | "select" | "creatableSelect" | "asyncSelect" | "searchSelect" | "currency" | "duration" | "timePicker" | "datePicker" | "dateRangePicker" | "searchInput" | "password"

valueRequired

Description

The value of the field. When used as a controlled component, the value should be updated as a result of the onChange event firing. This is handled by the external state. For components like checkbox lists, this value may contain an array of selected items.

Type

BaseOption | (BaseOption & Omit<ChipProps, "chipCheckboxProps"> & { chipCheckboxProps?: ChipCheckboxType & { onClick?: (thisItem: BaseOption) => void; }; })[] | string[] | ((string | number | readonly string[]) & string) | [string, string]

© 2025