import { FormField } from "@vitality-ds/components";
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); />
All FormField
components are comprised of the below elements:
- Labels: Inform users what the corresponding input fields mean.
- 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.
- 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.
- 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)
- Name: The name is a prop which is passed to all input fields. Name is a descriptive title
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.
-
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. -
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. -
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> ); };
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} />; };
A FormField
can display different input types.
- Text - Used for single lines of text that the user should have control over such as:
- Names
- Numbers
- Checkbox - Used to input set values, where more than one item can be selected such as:
- Contact availability (Morning, Afternoon, Evening, Night)
- Allergies (Nuts, Gluten, Dairy, Meat)
- RadioButtons - Used to input set values, where only one item from a mutually exclusive list can be selected such as:
- Preferred Method of contact (Email, Phone, Text)
- Prescribing Method (Print, E-Prescribing token, Direct to pharmacy)
- Password - Used to input a password:
- Login
- Currency - Used to process currency values:
- Invoices
- Payment values
- Duration - Used to display human readable durations:
- Appointment Durations
- Textarea - Used for multi-line text inputs such as:
- Patient notes
- Diary entries
- Select - Used to input set values:
- Country / State
- Or allow users to add their own from a list of premade suggestions with the creatableSelect type
- Use the asyncSelect type to make asynchronous calls to populate the select menu
- DatePicker - Used to input date values:
- Appointment/procedures
- Invoices
- Pregnancy records
- DateRangePicker - Used to input date values:
- Search Filtering by time period
- Setting the dates for events that span multiple days
- TimePicker - used to select a time from a list, or let users add their own time
- Selecting appointment times
- Generating invoices
- SearchInput - Used as a text input where the onChange function handles asynchronous functions.
- Searching through data tables
- Validation of name, BSB or other data matching requests
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} />; };
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 ownid
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> ); };
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} />; };
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} />; };
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} />; };
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} />; };
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
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} />; };
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> ); };
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} />; };
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} />; };
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} />; };
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} />; };
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} ] }>
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
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> ); };
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 anevent
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 usingtype="radioButtons"
should store this value in the state and update it with the new value as theonChange
is triggered.
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> ); };
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" />
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} />; };
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 will only affect the input
– so pass disabled: true
via inputProps
. As some FormField type
s 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); />
- Don't forget to include
aria-invalid
ininputProps
and set is as true/false
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
Description
Content to display a helper message.
Type
ReactNode
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
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
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">
Description
The text label to be displayed above the input field.
Type
string
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)
Description
Indicate whether or not the field's label should indicate a required field. Marked with an asterisk.
Type
boolean
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"
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]