Skip to content

PeoplePickerField

<PeoplePickerField /> is an input that provides a way to search for and select individuals or groups of people using an <AutocompleteField />-based experience.
A pointing to entire text field; B pointing to the FilterTag representing a selection; C pointing to the the count of hidden selections; D pointing to the value of the input; E pointing to the spinner indicating loading; F pointing to the clear button
The elements within the input portion of <PeoplePickerField />
The elements within the input portion of PeoplePickerField
ItemNameDescription
A<TextField />The base input of the <PeoplePickerField />
B<FilterTag />A selection represented by a <FilterTag /> using a name paired with either an individual's <Avatar /> or the <People /> icon for a group
CHidden selections countThe number of selections that are hidden when the input is not focused
DValueThe placeholder or typed text string used for filtering the displayed options
E<Spinner />Rendered when loading is true to give a visual cue that the options are loading
F<IconButton />The <Close /> icon is used within <IconButton /> to clear the entered and selected values
A pointing to the check icon of a selected option; B pointing to the persona containing the option information
The pieces of an option within a <PeoplePickerField /> listbox
Anatomy of a PeoplePickerField option
ItemNameDescription
A<Check />Indicates that the option is selected
B<Persona />The content of an option within <PeoplePickerField />
A basic PeoplePickerField implementation using Formik and Yup to validate that a person has been selected.

import React, { useMemo, useState } from 'react'
import { Formik } from 'formik'
import * as Yup from 'yup'
import {
FormLayout,
PeoplePickerField,
SubmitButton,
} from '@gusto/workbench-formik'
import { getRandomEmployee, useEmployeesQuery } from './_testData'
export const Basic = () => {
// 👇 track state of filter input query by passing `searchTerm` to `onDebouncedChange` prop
const [searchTerm, setSearchTerm] = useState('')
// 👇 fetch employees from an API, based on the current search term
const { data, loading } = useEmployeesQuery(searchTerm)
// 👇 format each employee to match `PeoplePickerOption` type;
const formattedFilteredEmployees = useMemo(
() =>
data?.map(employee => ({
// 👇 link to custom avatar here to use a unique image instead of the default Gusto illustration
avatarSrc: employee.Avatar,
// 👇 secondary line of text, displayed below the label in the option view
details: employee.Department,
// 👇 displayed text of the option
label: employee['Name-FL'],
// 👇 unique value of the option (i.e. UUID)
value: employee.Email,
})),
[data],
)
return (
<Formik
initialValues={{ employees: [] }}
validationSchema={Yup.object().shape({
employees: Yup.array().required('Required to pick an employee'),
})}
onSubmit={async values => {
await new Promise(resolve => {
setTimeout(resolve, 300 + Math.random() * 100)
})
alert(JSON.stringify(values, null, 2))
}}
>
<FormLayout
fields={
<PeoplePickerField
required
onDebouncedChange={e => setSearchTerm(e.target.value)}
name="employees"
label="Employees"
placeholder="Add an employee"
loading={loading}
options={formattedFilteredEmployees ?? []}
/>
}
actions={<SubmitButton>Submit</SubmitButton>}
/>
</Formik>
)
}

import React, { useMemo, useState } from 'react'
import { Formik } from 'formik'
import * as Yup from 'yup'
import type { PeoplePickerOption } from '@gusto/workbench'
import {
FormLayout,
PeoplePickerField,
SubmitButton,
} from '@gusto/workbench-formik'
import { getRandomEmployee, useEmployeesQuery } from './_testData'
const initialEmployees = [getRandomEmployee(), getRandomEmployee()].map(
employee => ({
details: employee.Department,
label: employee['Name-FL'],
value: employee.Email,
}),
)
/**
* To make initial selections in the Formik PeoplePickerField, set the `initialValues` prop on the Formik
* component to the initial values you want to pre-populate the field with. In order to render the selected
* tags with the appropriate label and avatar, you must also pass the selected options to the
* `selectedOptions` prop.
*/
export const WithInitialSelections = () => {
const [searchTerm, setSearchTerm] = useState('')
// 👇 fetch employees from an API, based on the current search term
const { data, loading } = useEmployeesQuery(searchTerm)
// 👇 format each employee to match `PeoplePickerOption` type;
const formattedFilteredEmployees = useMemo(
() =>
data?.map(employee => ({
details: employee.Department,
label: employee['Name-FL'],
value: employee.Email,
})) ?? [],
[data],
)
// Because we're setting initial values and Formik only has access to the id strings of
// the selected options, we need to take over display state management for the selected
// options so that the label and avatar data can be used.
// We start with the same initial employees array as the formik initialValues is using,
// and then update the selected options as the user interacts with the field via
// the onSelectedOptionsChange prop.
const [selectedEmployeeOptions, setSelectedEmployeeOptions] =
useState<PeoplePickerOption[]>(initialEmployees)
return (
<Formik
// 👇 initialValues is Formik's initial state of the id or value of the employees when the form is submitted
initialValues={{
employees: initialEmployees.map(employee => employee.value),
}}
validationSchema={Yup.object().shape({
employees: Yup.array().required('Required to pick an employee'),
})}
onSubmit={async values => {
await new Promise(resolve => {
setTimeout(resolve, 300 + Math.random() * 100)
})
alert(JSON.stringify(values, null, 2))
}}
>
<FormLayout
fields={
<PeoplePickerField
required
onDebouncedChange={e => setSearchTerm(e.target.value)}
name="employees"
label="Employees"
placeholder="Add an employee"
loading={loading}
// 👇 options is the filtered list of employees to select from
options={formattedFilteredEmployees}
// 👇 set the initial options with the full PeoplePickerOption data
selectedOptions={selectedEmployeeOptions}
// 👇 keep the selectedOptions in sync with the Formik values by updating the stored
// state with full data when selected options change
onSelectedOptionsChange={selectedOptions =>
setSelectedEmployeeOptions(selectedOptions)
}
/>
}
actions={<SubmitButton>Submit</SubmitButton>}
/>
</Formik>
)
}
Options for PeoplePickerField can either be individual people or groups of people. The &quo;group&quo; variant is used to visually represent an option that represents a group of people based on an attribute.

import React, { useMemo, useState } from 'react'
import { Formik } from 'formik'
import * as Yup from 'yup'
import {
FormLayout,
PeoplePickerField,
SubmitButton,
} from '@gusto/workbench-formik'
import { getRandomEmployee, useDepartmentsQuery } from './_testData'
export const WithGroupVariantOptions = () => {
const [searchTerm, setSearchTerm] = useState('')
const { data, loading } = useDepartmentsQuery(searchTerm)
const departments = useMemo(
() =>
data?.map(
department =>
({
details:
department.Count === 1
? `${department.Count} employee`
: `${department.Count} employees`,
label: department.Name,
value: department.Name,
// 👇 this sets the <People /> icon on the option and filtertag
variant: 'group',
}) as const,
),
[data],
)
return (
<Formik
initialValues={{ departments: [] }}
validationSchema={Yup.object().shape({
departments: Yup.array().required('Required to pick a department'),
})}
onSubmit={async values => {
await new Promise(resolve => {
setTimeout(resolve, 300 + Math.random() * 100)
})
alert(JSON.stringify(values, null, 2))
}}
>
<FormLayout
fields={
<PeoplePickerField
required
onDebouncedChange={e => setSearchTerm(e.target.value)}
name="departments"
label="Departments"
placeholder="Add a department"
loading={loading}
// 👇 pass in the options configured with groups
options={departments ?? []}
/>
}
actions={<SubmitButton>Submit</SubmitButton>}
/>
</Formik>
)
}
Options for PeoplePickerField can be visually grouped together in the listbox, which can make navigation easier by separating options into collections based on a shared attribute. This example shows employees grouped by their type of employment.

import React, { useMemo, useState } from 'react'
import { Formik } from 'formik'
import type {
PeoplePickerOption,
PeoplePickerOptionGroup,
} from '@gusto/workbench'
import {
FormLayout,
PeoplePickerField,
SubmitButton,
} from '@gusto/workbench-formik'
import {
type Employee,
getRandomEmployee,
useEmployeesQuery,
} from './_testData'
export const WithOptionGroups = () => {
// 👇 track state of filter input query by passing `searchTerm` to `onDebouncedChange` prop
const [searchTerm, setSearchTerm] = useState('')
// 👇 fetch employees from an API, based on current query
const { data, loading } = useEmployeesQuery(searchTerm)
// 👇 `options` need to be PeoplePickerOptions or PeoplePickerOptionGroup type
const formattedAndGroupedOptions = useMemo(() => {
const sortedEmployees =
data?.sort((a, b) => a.Employment.localeCompare(b.Employment)) ?? []
const groups: Record<string, Employee[]> = {}
// sort employees employment type and create groups by employment type
sortedEmployees.forEach((employee: Employee) => {
const employmentType = employee.Employment
const employeesInGroup = groups[employmentType] ?? []
employeesInGroup.push(employee)
groups[employmentType] = employeesInGroup
})
const options: PeoplePickerOptionGroup[] = []
// format options
Object.keys(groups).forEach(employmentType => {
if (employmentType) {
options.push({
label: employmentType,
options: groups[employmentType].map(
(employee: Employee): PeoplePickerOption => ({
label: employee['Name-FL'],
value: employee.Email,
variant: 'individual',
}),
),
})
}
})
return options
}, [data])
return (
<Formik
initialValues={{ employees: [] }}
onSubmit={values => {
alert(JSON.stringify(values, null, 2))
return Promise.resolve()
}}
>
<FormLayout
fields={
<PeoplePickerField
name="employees"
label="Employees"
placeholder="Search for an employee..."
onDebouncedChange={e => setSearchTerm(e.target.value)}
loading={loading}
options={formattedAndGroupedOptions}
/>
}
actions={<SubmitButton>Submit</SubmitButton>}
/>
</Formik>
)
}
PeoplePickerField is multi-select by default, but it can be rendered as a single-select field if only one person is to be selected.

import React, { useMemo, useState } from 'react'
import { Formik } from 'formik'
import {
FormLayout,
PeoplePickerField,
SubmitButton,
} from '@gusto/workbench-formik'
import { getRandomEmployee, useEmployeesQuery } from './_testData'
export const AsSingleSelect = () => {
const [searchTerm, setSearchTerm] = useState('')
const { data, loading } = useEmployeesQuery(searchTerm)
const employees = useMemo(
() =>
data?.map(employee => ({
label: employee['Name-FL'],
value: employee.Email,
})) ?? [],
[data],
)
return (
<Formik
initialValues={{ employee: '' }}
onSubmit={values => {
alert(JSON.stringify(values, null, 2))
return Promise.resolve()
}}
>
<FormLayout
fields={
<PeoplePickerField
label="Employee"
// 👇 set to single-select here
multiple={false}
name="employee"
placeholder="Search for an employee..."
onDebouncedChange={e => setSearchTerm(e.target.value)}
options={employees}
loading={loading}
/>
}
actions={<SubmitButton>Submit</SubmitButton>}
/>
</Formik>
)
}
React props
NameTypeDefaultDescription
debounceDelay  
number
200In milliseconds, the amount of time between value changes before the onDebouncedChange event is triggered
defaultSelectedOptions  
PeoplePickerOption[]
-The default options selected on initial render.
Note: When using the Formik version, you must use selectedOptions and Formik initialValues instead.
fit  
contentcontainer
containerDetermines the point of reference for the width of the component. If set to content, the size will be set by the size of the input. If set to container, the size will expand to fit the containing element.
helperText  
ReactNode
-A brief description or hint for the input; can be used to link to an external resource.
invalid  
boolean
-If true, the field will be shown in an invalid state.
label  Required
string
-Label associated with the input.
loading  
boolean
falseIf true, the loading Spinner is displayed.
multiple  
boolean
trueDetermines if field is rendered with multiple select or single select. Set to false to only allow one option to be selected.
onClear  
() => void
-Called when the ESCAPE key is pressed or the clear button is clicked.
onDebouncedChange  
(e: React.ChangeEvent<HTMLInputElement>) => void
-Called after the debounceDelay has passed since the last input value change.
onSelectedOptionsChange  
(options: PeoplePickerOption[]) => void
-Called when the selected options change.
optional  
boolean
-Used to indicate the field is optional
options  Required
PeoplePickerOptionOrGroup[]
-Options that are available for selection. Can be a flat list, with an array of PeoplePickerOption objects, or a grouped list, with an array of PeoplePickerOptionGroup objects. A group should include a label property for the group heading and an options property with an array of PeoplePickerOption objects.
selectedOptions  
PeoplePickerOption[]
-The data required to render the selected options when the component state is controlled externally.
Note: When using the workbench-formik component, this should be derived from the Formik field's value.
validationText  
ReactNode
-Validation message associated with the input.