Select

Select allows users to choose one option from a list of items in a Menu.

yarn add @workday/canvas-kit-react
Install
yarn add @workday/canvas-kit-react

Anatomy

Image of a Select Input in its default state.

  1. Label: Title of the input.
  2. Input Container: Rectangular container that houses the icon and placeholder text.
  3. Placeholder Text (Optional): Placeholder text like “Select One” is typically displayed in the Select field. After the user makes a selection, the placeholder text is replaced with the user’s selection.
  4. Icon: Caret icon positioned to the right of the container visually distinguishes this as a Select input.

Usage Guidance

  • Clicking or tapping anywhere in a Select opens the Menu.
  • A checkmark icon indicates which value is currently selected in the list.
  • Each Menu option should be distinct. If the option isn’t discrete, combine it with another option.
  • The list of Menu options should be sorted in a logical order alphabetically, chronologically, or by order of importance.

When to Use

  • Use Select as a form element where users are only allowed to select one item from a list of more than 7 predefined options.
  • Typically, Selects work best when the list is between 7 to 15 items to prevent overwhelming the user with too many options.

When to Use Something Else

  • Consider using a Switch if the only options are yes or no.
  • For a list between 2 to 7 predefined options, consider using Radio Buttons to select one option or Checkboxes to select multiple options. Radio and Checkbox groups display all options upfront and do not require the user to interact with the input to view the list of options.
  • Use a Prompt when the number of list items is large or unknown. Prompts have search capabilities and folders which provide users with the means to browse options. Prompts can be configured to support single or multi-select.

Examples

Basic Example

Select supports a dynamic API where you pass an array of items via the items prop and provide a render function to display the items. The items may be provided as an array of strings or an array of objects.

Select should be used in tandem with Form Field where the Select wraps the FormField element and the FormField element wraps the children of Select to meet accessibility standards. This ensures the label text from FormField is attached to the Select.Input and read out as a group for voiceover.

<Select items={options}>
<FormField label="Your Label">
<Select.Input onChange={e => handleChange(e)} id="contact-select" />
<Select.Popper>
<Select.Card>
<Select.List>{item => <Select.Item>{item.id}</Select.Item>}</Select.List>
</Select.Card>
</Select.Popper>
</FormField>
</Select>
Selected Value:

Our example uses React state to track the value of the Select.

Hoisted Model

By default, Select will create and use its own model internally. Alternatively, you may configure your own model with useSelectModel and pass it to Select via the model prop. This pattern is referred to as hoisting the model and provides direct access to its state and events outside of the Select component.

In this example, we set up external observation of the model state and create an external button to trigger an event to change the selected item.

Note: If your array of objects uses an id property and a text property there is no need to use the helper functions of getId or getTextValue. The collection system and the Select use these properties by default for keyboard navigation and selected the id based on the item clicked.

Selected Value: fax-3

Label Position

Set the labelPosition prop of the wrapping FormField to designate the position of the label relative to the Select. labelPosition accepts the following values:

  • FormField.LabelPosition.Top (Default)
  • FormField.LabelPosition.Left

Required

Set the required prop of the wrapping FormField to true to indicate that the field is required. Labels for required fields are suffixed by a red asterisk.

Selected Value:

Disabled

Set the disabled prop of Select.Input to prevent users from interacting with it.

Disabled Items

In order to disable items and prevent users from interacting with them:

  1. Set the nonInteractiveIds prop of Select to an array of disabled item ids. If your items are an array of strings this will be just the text value. If your items are an array of objects, this will be that value of the id property. This will disable interaction for those items and exclude them from type-ahead.

  2. Set the aria-disabled attribute of all disabled Select.Items to true. This ensures the items are styled as disabled.

The following example adds the string value of the items we want disable to nonInteractiveIds and sets aria-disabled for the disabled items.

Selected Value:

With Icons

Use Select.Item.Icon to render an icon for a Select.Item. The icon prop for Select.Item.Icon accepts system icons from @workday/canvas-system-icons-web.

In order to render the icon for the selected item in the Select.Input:

  1. Obtain a reference to the model by registering your items with useSelectModel.
  2. Get the selected item: const selectedItem = model.navigation.getItem(model.state.selectedIds[0], model)
  3. Pass the icon for the selected item to the input: <Select.Input inputStartIcon={selectedItem.value.icon}>

Note: data-id on Select.Item must match the id property in your array of objects. This ensures proper keyboard handling and type-ahead.

Note: that Select.Input will only render an icon if an item is selected.

Grow

Set the grow prop of the wrapping FormField to true to configure the Select.Input to expand to the width of its container.

Select.Card has a default maximum height of 300px to restrict the height of the dropdown menu. Set its maxHeight prop to override this value.

Ref Forwarding

Select.Input supports ref forwarding. It will forward ref to its underlying <input type="text" role="combobox"> element.

Error States

Set the error prop of the wrapping FormField to FormField.ErrorType.Alert or FormField.ErrorType.Error to set the Select to the alert or error state, respectively. You will also need to set the hintId and hintText props on the FormField to meet accessibility standards. You must set an id attribute on the Select.Input element that matches the value of inputId set on the FormField element. These attributes ensure that the alert message is associated to the Select and read out by voiceover.

Note: The Select container component, Select, must wrap FormField to ensure Select.Input is styled correctly.

<Select items={options}>
<FormField label="Contact" inputId="contact-id-formfield">
<Select.Input id="contact-id-formfield" />
...
</FormField>
</Select>

Alert

Use the alert state when a selection is valid but there is additional information.

Alert: Please choose a form of contact.

Selected value:

Error

Use the error state when the selection is no longer valid.

Error: Fax is disabled. Please choose a different option.

Selected Value:

Initial Selected Item

You can set initialSelectedIds to the value that you want initially selected.

Id: da594226446c11de98360015c5e6daf6

Value: English (United States)

Placeholder

You can change the placeholder text by passing in a string value to the placeholder attribute on the Select.Input.

Selected Value:

Fetching Dynamic Items

It's common to load items from a server call. Hoisting the model and setting your items on state allows you to pass those items to your model. You can leverage React state to set your items on load as well as displaying a placeholder indicating when items are loaded.

Note: In this case we need to use getId and getTextValue because our data doesn't have the properties of id or text. Using these helper functions sets the serverId to be id and label to be text.

Foo Bar
Selected Id: 456
Selected value:

Complex

When registering items in an array of objects, it's common to have the text that is displayed to the user be different than an id. In this example, serverId and label properties need to be remapped to id and text hence the usage of getId and getTextValue. If your object has the properties text and id, there would be no need for this.

Id:

Value:

Note: By default, the identifier and text value are id and text properties respectively. If your data object for each item is different, provide a getId or getTextValue function to the model config. For example:

const items = [
{
serverId: '1',
label: 'First Option',
},
];
<Select items={items} getId={item => item.serverId} getTextValue={item => item.label}>
{/* etc */}
</Select>;

When to use getId, or getTextValue

  • getId: This is an optional function to return the id of an item. If not provided, the default function will return the id property from the object of each item. If you did not provide items, do not override this function. Instead provide static items via JSX. the list will create an internal array of items where id is the only property and the default getId will return the desired result. Note: If your array of objects has a different property for id, like serverId, use this function to set the id.

    const options = [{text: 'Pizza', serverId: 'pizza-1'}, {text: 'Cheeseburger', serverId: 'cheeseburger'}]
    <Select items={options} getId={(item) => item.serverId}>
    <FormField label="Your Label">
    <Select.Input onChange={e => handleChange(e)} id="contact-select" />
    <Select.Popper>
    <Select.Card>
    <Select.List>{item => <Select.Item>{item.text}</Select.Item>}</Select.List>
    </Select.Card>
    </Select.Popper>
    </FormField>
    </Select>
  • getTextValue: Optional function to return the text representation of an item. If not provided, the default function will return the text property of the object of each item or an empty string if there is no text property. If you did not provide items, do not override this function. Note: If your array of objects has a different property for text, like label, use this function to set the text.

    const options = [{label: 'Pizza', id: 'pizza-1'}, {label: 'Cheeseburger', id: 'cheeseburger'}]
    <Select items={options} getTextValue={(item) => item.label}>
    <FormField label="Your Label">
    <Select.Input onChange={e => handleChange(e)} id="contact-select" />
    <Select.Popper>
    <Select.Card>
    <Select.List>{item => <Select.Item>{item.label}</Select.Item>}</Select.List>
    </Select.Card>
    </Select.Popper>
    </FormField>
    </Select>

Component API

Select

Use Select to allow users to choose an option from a list or type characters to select a matching option.

Note: Select must wrap FormField and FormField must wrap all Select children to ensure proper accessibility.

<Select items={options}>
<FormField label="Your Label">
<Select.Input onChange={e => handleChange(e)} id="contact-select" />
<Select.Popper>
<Select.Card>
<Select.List>
{item => <Select.Item>{item.id}</Select.Item>}
</Select.List>
</Select.Card>
</Select.Popper>
</FormField>
</Select>

Props

Props extend from . If a model is passed, props from SelectModelConfig are ignored.

NameTypeDescriptionDefault
theme
childrenReactNode

Children of the Combobox. Should contain a Combobox.Input and a Combobox.Menu

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

Select.Input

Select.Input renders a that handles keyboard navigation and interaction defined by WAI. This component can either be controlled or uncontrolled.

<Select items={options}>
<FormField label="Contact">
<Select.Input onChange={(event) => handleChange(event)}>
...
</FormField>
</Select>

Props

Props extend from . Changing the as prop will change the element interface.

NameTypeDescriptionDefault
inputStartIcon

The Icon to render at the start of the input. Use this prop if your options include icons that you would like to render in the input when selected. Note:An option must be selected in order to render and icon.

error

The type of error associated with the TextInput (if applicable).

width number string

The width of the TextInput.

theme
growboolean

True if the component should grow to its container's width. False otherwise.

cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';
// ...
// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.
return (
<Element
{...handleCsProp(elemProps, [
myStyles,
myModifiers({ size: 'medium' }),
myVars({ backgroundColor: 'red' })
])}
>
{children}
</Element>
)
childrenReact.ReactNode
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

refReact.Ref<R = >

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useSelectInput

useSelectInput extends and and adds type ahead functionality and Select-specific keyboard support.

(
  (
    model: ,
    elemProps: {
      keySofar: string;
      placeholder: string;
    },
    ref: React.Ref
  ) => {
    onKeyDown: (event: ) => void;
    onChange: (event: <>) => void;
    autoComplete: 'off';
    onFocus: () => void;
    textInputProps: {
      ref: <>;
    };
    ref: (instance:  null) => void;
    defaultValue:  string undefined;
  },
  ,
  ,
  ,
  
)

Select.Card

Select.Card renders a . You have access to all Card props.

Note: The card will be the width of its corresponding Select.Input.

<Select item={options}>
<FormField label="Your Label">
<Select.Input onChange={(event) => handleChange(event)}>
<Select.Popper>
<Select.Card>
...
</Select.Card>
</Select.Popper>
</FormField>
</Select>

Layout Component

Select.Card supports all props from thelayout component.

Props

Props extend from div. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
cs

The cs prop takes in a single value or an array of values. You can pass the CSS class name returned by , or the result of and . If you're extending a component already using cs, you can merge that prop in as well. Any style that is passed to the cs prop will override style props. If you wish to have styles that are overridden by the css prop, or styles added via the styled API, use wherever elemProps is used. If your component needs to also handle style props, use instead.

import {handleCsProp} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';
// ...
// `handleCsProp` handles compat mode with Emotion's runtime APIs. `mergeStyles` has the same
// function signature, but adds support for style props.
return (
<Element
{...handleCsProp(elemProps, [
myStyles,
myModifiers({ size: 'medium' }),
myVars({ backgroundColor: 'red' })
])}
>
{children}
</Element>
)
childrenReact.ReactNode
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

div
refReact.Ref<R = div>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

useSelectCard

Sets the width of the SelectCard to the Select.Input width.

(
  model: ,
  elemProps: {},
  ref: React.Ref
) => {
  width: number;
}

Select.Item

Select.Item renders a with aria role of option. You can optionally render a Icon.

<Select item={options}>
<FormField label="Your Label">
<Select.Input onChange={(event) => handleChange(event)}>
<Select.Popper>
<Select.Card>
<Select.List>
{(item) => <Select.Item><Select.Item.Icon icon={icon} />{item}</Select.Item>}
</Select.List
</Select.Card>
</Select.Popper>
</FormField>
</Select>

Props

Props extend from li. Changing the as prop will change the element interface.

NameTypeDescriptionDefault
childrenReactNode
asReact.ElementType

Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using React.forwardRefand spread extra props to a root element.

Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care.

li
refReact.Ref<R = li>

Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If as is set to an element, it will be that element. If as is a component, the reference will be to that component (or element if the component uses React.forwardRef).

model

Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context.

elemPropsHook(
  model: ,
  elemProps: TProps
) => HTML Attributes

Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one.

Model

useSelectModel

SelectModel extends the . Selecting items from the menu will dispatch an input event on the input which should work with form libraries, automation and autofill.

const model = useSelectModel({items: ['Mobile', 'Phone', 'E-Mail']})
<Select model={model}>
...
</Select>
useSelectModel (config: ):

Specifications

GivenWhenThen
given the "Disabled Options" story is rendered
    • it should not have any axe errors
    given the "Disabled Options" story is rendered
      • it should not have an aria-activedescendant attribute
      given the "Disabled Options" story is rendered
      • the select button is clicked
      • it should not have any axe errors
      given the "Disabled Options" story is rendered
      • the select button is clicked
      • the select should have an aria-expanded attribute set to "true"
      given the "Disabled Options" story is rendered
      • the select button is clicked
      • the menu should be visible
      given the "Disabled Options" story is rendered
      • the select button is clicked
      • the menu should have an aria-activedescendant attribute with the same value as the id of the first option ("E-mail")
      given the "Disabled Options" story is rendered
      • the select button is clicked
      • the first option ("E-Mail") should have an aria-selected attribute set to "true"
      given the "Disabled Options" story is rendered
      • the select button is clicked
      • AND THEN the "Phone" option (with the value "phone") is clicked
      • the select input should read "Phone"
      given the "Disabled Options" story is rendered
      • the select button is clicked
      • AND THEN the "Phone" option (with the value "phone") is clicked
      • the select input should re-acquire focus
      given the "Disabled Options" story is rendered
      • the select button is clicked
      • AND THEN the "Phone" option (with the value "phone") is clicked
      • the menu should not be visible
      given the "Disabled Options" story is rendered
      • the select button is clicked
      • AND THEN the "Phone" option (with the value "phone") is clicked
      • AND THEN the menu is opened again
      • the menu should set assistive focus to the "Phone" option
      given the "Disabled Options" story is rendered
      • the select input is focused and down arrow key is pressed
      • the select input should have an aria-expanded attribute set to "true"
      given the "Disabled Options" story is rendered
      • the select input is focused and down arrow key is pressed
      • the menu should be visible
      given the "Disabled Options" story is rendered
      • the select input is focused and down arrow key is pressed
      • AND THEN the down arrow key is pressed for a second time
      • the menu should set assistive focus to the "Phone" option
      given the "Disabled Options" story is rendered
      • the select input is focused and down arrow key is pressed
      • AND THEN the down arrow key is pressed for a second time
      • AND THEN the down arrow key is pressed for a third time
      • the menu should set assistive focus to the "Mail" option and skip disabled fax
      given the "Disabled Options" story is rendered
      • the enter key is pressed
      • the menu should be visible
      given the "Disabled Options" story is rendered
      • the enter key is pressed
      • the menu should have E-Mail selected
      given the "Disabled Options" story is rendered
      • the enter key is pressed
      • AND THEN mail option is selected using arrow keys
      • it should read "Mail"
      given the "Disabled Options" story is rendered
      • the enter key is pressed
      • AND THEN mail option is selected using arrow keys
      • it should re-acquire focus
      given the "Disabled Options" story is rendered
      • the enter key is pressed
      • AND THEN mail option is selected using arrow keys
      • the menu should not be visible after selection
      given the "Disabled Options" story is rendered
      • the up arrow key is pressed
      • the menu should set assistive focus to the "E-mail" option
      Source: Select.spec.ts

      Accessibility Guidelines

      Keyboard Interaction

      Each Select must have a focus indicator that is highly visible against the background and against the non-focused state. Refer to Accessible Colors for more information.

      Select must support the following keyboard interactions:

      • Tab: focus the Select component
      • Enter or Space: open the Select list box and focus the first option
      • Esc: dismiss the Select list box and focus the Select component
      • Up Arrow or Down Arrow: focus the previous or next option respectively
      • Character Key: focus options matching character key
      • Home or Fn + Up Arrow: focus first option
      • End or Fn + Down Arrow: focus last option

      Screen Reader Interaction

      Select must communicate the following to users:

      • This component is a “combo” or “combobox”
      • The associated label
      • The currently selected value
      • The “collapsed” or “expanded” state
      • When opened, there is a list of ‘X’ items
      • When opened, the name of the active option
      • When opened, the position “X of Y” for the active option

      Design Annotations Needed

      No design annotations required for Select.

      Implementation Markup Needed

      The Select component is built around the ARIA Combobox v1.2 specification for Select-Only. Support for this component may be limited by browser vendors and/or screen readers. Also, note any reference to “menu” on this page does not refer to an ARIA menu pattern. The Select component is using an ARIA listbox pattern for the options.

      • A <label> element must be used with a for attribute referencing the unique id value of the Select input.
      • Select input must have a required attribute when the field is required for form submission.
      • Select input must have an aria-describedby attribute referencing the unique id value of the inline hint text below the field.
      • Select input must have an aria-invalid=”true” attribute when the field has an error condition.
      • Select input must have a disabled attribute when the field is disabled.

      Content Guidelines

      • The list of Menu items should be scannable, with concise labels written in title case. Don’t write sentences and omit articles (a, an, the) unless needed for clarity. For more detailed information on how to write Menu items, refer to the Drop-down Menus section of the Content Style Guide.
      • Placeholder text for a Select must begin with the verb “Select”. Refer to the guidelines on Placeholder Text in the Content Style Guide for more tips on how to write placeholder text.

      Can't Find What You Need?

      Check out our FAQ section which may help you find the information you're looking for.

      FAQ Section