Select

Jump to Props

Simplify your data selection with the Select component - select from a predefined or asynchronously called list or add your own options.

Import

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

Usage

The Select component reduces the possibility of human error by offering a predefined list of options for the user to choose from. This eliminates the need for manual input and reduces the chance of typos or incorrect values being entered. By utilizing the Select component, you can ensure accurate data input and increase the reliability of your forms.

You can pass the value as a string and the corresponding label will be rendered, as long as the value exists in the options. These value strings can be passed in an array when isMulti={true}.

Building a form? Checkout FormField

Name and id

The name prop describes what the select input is for. Consider it the key to the values within. If a list of states is presented, name would be equal to "state". id pairs the select input with the associated label in the context of a form.

<DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
  <label htmlFor="state">State</label>
  <Select
    onChange={(newValue) => console.log(newValue)}
    name="state"
    id="state"
    options={[
      { label: "QLD", value: "qld" },
      { label: "NSW", value: "nsw" },
      { label: "TAS", value: "tas" },
      { label: "VIC", value: "vic" },
      { label: "WA", value: "wa" },
      { label: "SA", value: "sa" },
      {
        label: "ACT",
        value: "act",
      },
      { label: "NT", value: "nt" },
    ]}
  />
</DocsFlex>

Options

Options are the list of values that the select shows when open. Options can be individual list items, or they can be grouped with a heading. The options should be an array of objects, which each object containing a label (the displayed text) and value (the value of the option). Below shows the data structure for both:

const options = [
  { label: "Option one", value: "optionOne" },
  { label: "Option two", value: "optionTwo" },
];

const groupedOptions = [{ label: "First group", options: [...options] }];

// both of these are valid options to pass:
// <Select options={options} />
// <Select options={groupedOptions} />
() => {
  const [selectedValue, setSelectedValue] = React.useState();
  const singleListOptions = [
    { label: "QLD", value: "qld" },
    { label: "NSW", value: "nsw" },
    { label: "TAS", value: "tas" },
    { label: "VIC", value: "vic" },
    { label: "WA", value: "wa" },
    { label: "SA", value: "sa" },
    {
      label: "ACT",
      value: "act",
    },
    { label: "NT", value: "nt" },
  ];

  const groupedListOptions = [
    {
      label: "Territories",
      options: [
        {
          label: "ACT",
          value: "act",
        },
        { label: "NT", value: "nt" },
      ],
    },
    {
      label: "States",
      options: [
        { label: "QLD", value: "qld" },
        { label: "TAS", value: "tas" },
        { label: "NSW", value: "nsw" },
        { label: "VIC", value: "vic" },
        { label: "WA", value: "wa" },
        { label: "SA", value: "sa" },
      ],
    },
  ];

  return (
    <DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
      <label htmlFor="states">Standard states</label>
      <Select
        id="states"
        name="states"
        placeholder="Select a state..."
        options={singleListOptions}
        value={selectedValue}
        onChange={(newValue) => setSelectedValue(newValue)}
      />
      <label htmlFor="groupedStates">Grouped states/territories</label>
      <Select
        id="groupedStates"
        name="groupedStates"
        placeholder="Select a state..."
        options={groupedListOptions}
        onChange={(newValue) => console.log(newValue)}
      />
    </DocsFlex>
  );
};

Note that grouped and non-grouped options can be combined into one list, but that groups cannot have child groups.

<DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
  <label htmlFor="appointmentType">Appointment type</label>
  <Select
    onChange={(newValue) => console.log(newValue)}
    name="appointmentType"
    options={[
      { label: "Standard appointment", value: "standard" },
      { label: "Results discussion", value: "results" },
      { label: "Immunisation", value: "immunisation" },
      {
        label: "Longer appointment (30 mins)",
        options: [
          { label: "New patient", value: "newPatient" },
          { label: "Skin check", value: "skinCheck" },
        ],
      },
    ]}
  />
</DocsFlex>

Status Badge

The Status Badge within the options offers an additional indicator for users to help them make a choice. If an option has a statusBadgeProps included, it can be controlled with label, severity, and importance. Some common use cases are Default, eBookings Enabled, Permission Status.

Aim For
  • It is recomended to keep the StatusBadge Label short and concise.
<DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
  <label htmlFor="state">State</label>
  <Select
    onChange={(newValue) => console.log(newValue)}
    name="state"
    id="state"
    options={[
      {
        label: "Royal Brisbane Hospital",
        value: "rbh",
        statusBadgeProps: {
          label: "Default",
        },
      },
      {
        label: "NSW",
        value: "nsw",
        statusBadgeProps: {
          label: "Status Badge",
        },
      },
      { label: "TAS", value: "tas" },
      { label: "VIC", value: "vic" },
      {
        label: "WA",
        value: "wa",
        statusBadgeProps: {
          label: "Status Badge Critical",
          severity: "critical",
        },
      },
      { label: "SA", value: "sa" },
      {
        label: "ACT",
        value: "act",
      },
      {
        label: "NT",
        value: "nt",
        statusBadgeProps: {
          label: "Status Badge Success",
          severity: "success",
        },
      },
    ]}
  />
</DocsFlex>

Error state

To communicate an invalid state for the select input, pass hasError to the select.

<DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
  <label htmlFor="title">Title</label>
  <Select
    hasError
    name="title"
    id="title"
    placeholder="Select a title..."
    onChange={(newValue) => console.log(newValue)}
    options={[
      { label: "Dr", value: "dr" },
      { label: "Miss", value: "miss" },
      { label: "Ms", value: "ms" },
      { label: "Mrs", value: "mrs" },
      { label: "Mr", value: "mr" },
    ]}
  />
</DocsFlex>

Placeholder

Pass a placeholder string to give a description of what the user is going to select. Placeholders are optional and often start with "Select a..." as a hint to the user. The select component defaults a placeholder to "Select" if none is passed. Placeholders should never replace labels for accessibility and user experience purposes.

<DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
  <label htmlFor="title">Title</label>
  <Select
    name="title"
    id="title"
    placeholder="Select a title..."
    onChange={(newValue) => console.log(newValue)}
    options={[
      { label: "Dr", value: "dr" },
      { label: "Miss", value: "miss" },
      { label: "Ms", value: "ms" },
      { label: "Mrs", value: "mrs" },
      { label: "Mr", value: "mr" },
    ]}
  />
</DocsFlex>

Disabled

There are some circumstances where the select needs to be disabled, for example admin vs user accessibility. To disable the entire input, pass disabled at the root level. To disable an individual option, pass it in the options prop.

<Stack align="stretch">
  <DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
    <label htmlFor="title">Disabled select</label>
    <Select
      disabled
      name="title"
      id="title"
      onChange={(newValue) => console.log(newValue)}
      options={[
        { label: "Dr", value: "dr" },
        { label: "Miss", value: "miss" },
        { label: "Ms", value: "ms" },
        { label: "Mrs", value: "mrs" },
        { label: "Mr", value: "mr" },
      ]}
    />
  </DocsFlex>
  <DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
    <label htmlFor="appointmentType">Disabled option</label>
    <Select
      name="appointmentType"
      id="appointmentType"
      placeholder="Appointment type..."
      onChange={(newValue) => console.log(newValue)}
      options={[
        { label: "Standard appointment", value: "standard" },
        { label: "Results discussion", value: "results" },
        { label: "Immunisation", value: "immunisation" },
        { label: "New patient", value: "newPatient", disabled: true },
        { label: "Skin check", value: "skinCheck" },
      ]}
    />
  </DocsFlex>
</Stack>

Multiple select

If the user should be able to choose more than one item from the list of options, pass the isMulti prop. Read below for the more specific chip type.

() => {
  const [selectedValue, setSelectedValue] = React.useState([
    {
      label: "Wheat",
      value: "wheat",
    },
    {
      label: "Shellfish",
      value: "shellfish",
    },
  ]);

  return (
    <Select
      isMulti
      name="foodAllergies"
      id="foodAllergies"
      value={selectedValue}
      onChange={(newValue) => setSelectedValue(newValue)}
      options={[
        { label: "Nuts", value: "nuts" },
        { label: "Dairy", value: "dairy" },
        { label: "Wheat", value: "wheat" },
        { label: "Soy", value: "soy" },
        { label: "Shellfish", value: "shellfish" },
        { label: "Fish", value: "fish" },
        { label: "Egg", value: "egg" },
      ]}
    />
  );
};

Configuring Chips

Aside from the basic label, a multi select's "Chips" can have additional configuration. These props mirror the props of the basic Chip.

() => {
  const [selectedValue, setSelectedValue] = React.useState([
    {
      label: "Dr Vivian Jones",
      value: "drvivian",
      metaLabelProps: {
        label: "Mail",
      },
      iconProps: {
        icon: Practitioner,
      },
    },
    {
      label: "Dr. Mary McClusky",
      value: "dairy",
      metaLabelProps: {
        label: "Email",
      },
      iconProps: {
        icon: Practitioner,
      },
    },
  ]);

  return (
    <Select
      name="practitioners"
      id="practitioners"
      value={selectedValue}
      onChange={(newValue) => setSelectedValue(newValue)}
      isMulti
      options={[
        {
          label: "Practitioners",
          options: [
            {
              label: "Dr Vivian Jones",
              value: "drvivian",
              metaLabelProps: {
                label: "Mail",
              },
              iconProps: {
                icon: Practitioner,
              },
            },
            {
              label: "Dr. Mary McClusky",
              value: "dairy",
              metaLabelProps: {
                label: "Email",
              },
              iconProps: {
                icon: Practitioner,
              },
            },
            {
              label: "Dr Andrew Demo",
              value: "andrewdemo",
              metaLabelProps: {
                label: "Mail",
              },
              iconProps: {
                icon: Practitioner,
              },
            },
          ],
        },
        {
          label: "Patients",
          options: [
            {
              label: "Deirdre Johnson",
              value: "djohnson",
              metaLabelProps: {
                label: "Email",
              },
              iconProps: {
                icon: Patient,
              },
            },
            {
              label: "Xander McClusky",
              value: "xmc",
              metaLabelProps: {
                label: "Email",
              },
              iconProps: {
                icon: Patient,
              },
            },
          ],
        },
      ]}
    />
  );
};

Checkbox with a Chip (Alpha)

Whilst this UX pattern is not common, users may wish to augment the value of a selected option once it's been added to the Select. For instance, indicating in a list of multiple procedures if additional procedures are provisional/conditional.

Please note that the initial state of the Select's options are separate to the Select's value that gets updated upon clicking the Chip's checkbox. Do not expect the original options data to be changed as the checkbox toggles. As can be seen in the below example, the Select's value changes.

() => {
  const [isLoading, setIsLoading] = React.useState(false);
  const [selectedValue, setSelectedValue] = React.useState([]);
  const [isSubmitted, setIsSubmitted] = React.useState(false);

  return (
    <DocsBox css={{ width: "100%" }}>
      <Stack align="stretch">
        <Select
          name="procedureName"
          id="procedureName"
          value={selectedValue}
          onChange={(newValue) => setSelectedValue(newValue)}
          isMulti
          options={[
            {
              label: "BAAF Adult 1/2 - Adult adoption examination",
              value: "baaf",
              chipCheckboxProps: {
                label: "+/-",
                isChecked: false,
                tooltipContent: "Mark as provisional/conditional",
              },
            },
            {
              label:
                "Replacement of cardiac pacemaker device with dual-chamber device",
              value: "replace",
              chipCheckboxProps: {
                label: "+/-",
                isChecked: true,
                tooltipContent: "Mark as provisional/conditional",
              },
            },
          ]}
        />
        <Stack direction="horizontal" align="end">
          <Button appearance="primary" onClick={() => setIsSubmitted(true)}>
            Render Select Value
          </Button>
          {isSubmitted && (
            <Button onClick={() => setIsSubmitted(false)}>Hide value</Button>
          )}
        </Stack>
        {isSubmitted && JSON.stringify(selectedValue)}
      </Stack>
    </DocsBox>
  );
};

If you do not wish for the select to open when interacting with the checkbox, you can set the openMenuOnClick prop to false.

Disabling a chip's checkbox after clicking

In some advanced cases, you may wish to disable a chip's checkbox once it has been clicked. For instance, a checkbox may represent an acknowledged state of a clinical letter and once it has been acknowledged, it cannot be undone. To do this, you can pass a function to the onClick of the chipCheckboxProps that updates the internal state of the Select component.

The below example shows how to update the Select component's internal state. In practice, you would also want to ensure that after saving those changes, the selectedValue passed to the component also passes isDisabled to the selection passed in.

() => {
  const [selectedValue, setSelectedValue] = React.useState([]);

  const handleChange = (thisItem) => {
    thisItem.chipCheckboxProps.isDisabled = true;
    thisItem.chipCheckboxProps.tooltipContent =
      "Acknowledged on 13/03/24 by Dr. Claire Fabrice";
  };

  return (
    <DocsBox css={{ width: "100%" }}>
      <Stack align="stretch">
        <Select
          name="letters"
          id="letters"
          value={selectedValue}
          onChange={(newValue) => setSelectedValue(newValue)}
          isMulti
          options={[
            {
              label: "Letter to Dr Jemima Chambers",
              value: "jchambers",
              chipCheckboxProps: {
                label: "Ack",
                isChecked: false,
                tooltipContent: "Acknowledge this letter",
                onClick: handleChange,
              },
            },
          ]}
        />
      </Stack>
    </DocsBox>
  );
};

Clicking on a Chip

In advanced cases, you may wish to let the user click on a chip and perform an action (such as show a popover or open a dialog). In order to do this, you need to pass an onClick function to the option data as well as setting openMenuOnClick=false on the Select component. If you do not set this prop to false, the Select component will perform both the onClick and the default behaviour which is to open the select menu.

Note: This demo is not working as the component is in alpha

() => {
  const [isAcknowledged, setIsAcknowledged] = React.useState(false);
  const [selectedValue, setSelectedValue] = React.useState([]);

  return (
    <Select
      name="foodAllergies"
      id="foodAllergies"
      value={selectedValue}
      onChange={(newValue) => setSelectedValue(newValue)}
      isMulti
      openMenuOnClick={false}
      options={[
        {
          label: "Practitioners",
          options: [
            {
              label: "Dr Vivian Jones",
              value: "drvivian",
              onClick: () => alert("You clicked the Chip"),
              metaLabelProps: {
                label: "Mail",
              },
              iconProps: {
                icon: Practitioner,
              },
            },
          ],
        },
      ]}
    />
  );
};

Custom Option Rendering

The renderOption function allows the user to define how they want to display the options. There are a couple of parameters available to the user to render these choices:

  • data: contains all data passed from the option, this includes the value, label or any custom data in insert into the object, all of which are accessible.
  • states: an object containing, isFocused, isSelected, isDisabled each of which will either be undefined or true, allowing you to create custom rendering options based off these values

Combo these states with Vitality's custom styled components API and variants in the styling!!!

The following example illustrates how this could be employed to present full billing item code descriptions on hover or focus, thereby conserving space on the screen.

() => {
  const [select, setSelect] = React.useState("");

  const Wrap = styled("div", {
    width: "100%",
    display: "flex",
    position: "relative",
    boxSizing: "border-box",
    textAlign: "left",
    alignItems: "center",
    justifyContent: "flex-start",
    fontSize: 12,

    "&:hover": {
      "& > .description": {
        whiteSpace: "normal",
      },
    },

    variants: {
      isFocused: {
        true: {
          "& > .description": {
            whiteSpace: "normal",
          },
        },
      },
    },
  });

  const CodeSpan = styled("span", {
    textAlign: "left",
    width: 110,
    height: 16,
    display: "flex",
    position: "relative",
    overflow: "hidden",
    flexShrink: 0,
  });

  const DescriptionSpan = styled("span", {
    flex: "1 1 auto",
    padding: "0 16px",
    minWidth: 0,
    width: 500,
    whiteSpace: "nowrap",
    textOverflow: "ellipsis",
    overflow: "hidden",
  });

  const CustomOptionRender = (option, states) => {
    const { isFocused, isDisabled, isSelected } = states;
    return (
      <Wrap isFocused={isFocused}>
        <CodeSpan>{option.code}</CodeSpan>
        <DescriptionSpan className="description">
          {option.description}
        </DescriptionSpan>
      </Wrap>
    );
  };

  return (
    <DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
      <label htmlFor="billingItem">Billing Item</label>
      <Select
        placeholder="Select A Billing Item"
        name="billingItem"
        id="billingItem"
        onChange={(newValue) => setSelect(newValue.value)}
        value={select}
        options={[
          {
            label: "12001",
            value: "12001",
            code: "12001",
            description:
              "Skin prick testing for aeroallergens, including all allergens tested on the same day, not being a service associated with a service to which item 12000, 12002, 12005, 12012, 12017, 12021, 12022 or 12024 applies. Applicable only once in any 12 month period",
          },
          {
            label: "12002",
            value: "12002",
            code: "12002",
            description:
              "Repeat skin prick testing of a patient for aeroallergens, including all allergens tested on the same day, if: (a) further testing for aeroallergens is indicated in the same 12 month period to which item 12001 applies to a service for the patient; and (b) the service is not associated with a service to which item 12000, 12001, 12005, 12012, 12017, 12021, 12022 or 12024 applies Applicable only once in any 12 month period",
          },
          {
            label: "12003",
            value: "12003",
            code: "12003",
            description:
              "Skin prick testing for food and latex allergens, including all allergens tested on the same day, not being a service associated with a service to which item 12012, 12017, 12021, 12022 or 12024 applies",
          },
          {
            label: "12004",
            value: "12004",
            code: "12004",
            description:
              "Skin testing for medication allergens (antibiotics or non general anaesthetics agents) and venoms (including prick testing and intradermal testing with a number of dilutions), including all allergens tested on the same day, not being a service associated with a service to which item 12012, 12017, 12021, 12022 or 12024 applies",
          },
        ]}
        renderOption={CustomOptionRender}
      />
    </DocsFlex>
  );
};

MenuPlacement Provides control of the placement of the menu in relation to the select input. valid options are "auto" (default), "top" and "bottom"

  • "auto": will open the menu both above or below the select depending on available space, both in regards to the edge of it's parent and the edge of the viewport. The scroll will always try to go down, and will scroll the viewport if that is an option. This option is the default

    • The prop minMenuHeight takes a number representing a pixel value that can be set to manipulate the point at which the switch occurs.
  • "top": will always render the menu above the menu, regardless of where the select is in the viewport

  • "bottom": will always render the menu below the menu, regardless of where the select is in the viewport

() => {
  const options = [
    { label: "QLD", value: "qld" },
    { label: "NSW", value: "nsw" },
  ];

  return (
    <DocsFlex css={{ flexGrow: 1, flexDirection: "row", gap: 8 }}>
      <DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
        <label>menuPlacement="auto"</label>
        <Select
          onChange={(newValue) => console.log(newValue)}
          name="state"
          id="state"
          options={options}
          menuPlacement="auto"
        />
      </DocsFlex>
      <DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
        <Select
          onChange={(newValue) => console.log(newValue)}
          name="state"
          id="state"
          options={options}
          menuPlacement="top"
        />
        <label>menuPlacement="top"</label>
      </DocsFlex>
      <DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
        <label>menuPlacement="bottom"</label>
        <Select
          onChange={(newValue) => console.log(newValue)}
          name="state"
          id="state"
          options={options}
          menuPlacement="bottom"
        />
      </DocsFlex>
    </DocsFlex>
  );
};

The method of positioning the element in the DOM is an important decision to make to ensure that use of the select doesn't cause any unwanted UI behaviour that might impact user experience.

The following props are all ways of manipulating how and where the menu us rendered and therefore some options might be better then others given the use-case. it's important to test thoroughly to ensure the right solution for you.

  1. menuPosition": menuPosition provides control of the position css property of the menu. This impacts layering and changes some placement behaviour, try to find which example is best for your use-case. Valid options are "absolute" (default) or "fixed".

  2. portalMenu: Dictates weather the menu should be portalled to the document body. This is useful when you don't want the menu to potentially cause the page to shift or you specifically don't want the portal in document flow. It will always portal to document.body, which can cause scrolling issues if the input is inside a scrollable element and the menu is not set to menuPlacement: "auto". If the menu is portalled this prop is ignored and the position is set to "absolute" as absolute position in required to place correctly when portalled

Type-ahead Selects

In certain scenarios, you may want your select component to function more like a TextInput, allowing users to input custom values while still offering contextualized suggestions from a select menu. The valueRenderType prop facilitates this functionality. When you set valueRenderType="typeahead", it triggers adjustments in both the behaviour of the Select component and the required props.

Firstly, when using valueRenderType="typeahead", the Select component must now be a controlled component. Consequently, the value is now expected to be a string, although if the value is passed as a vitality select object then vitality will try to render a string value from the option.label or option.value preferring the label. You must provide an onChange handler, which should return a string value to update the value when an option is selected from the dropdown. Typically, this would look like: onChange={(selectedOption) => setValue(selectedOption.label)}. Additionally, you'll need to include an onInputChange handler, which operates similarly to any text input handler: onInputChange={(newValue) => setValue(newValue)}.

These handlers function for both AsyncSelect and regular Select's. For CreatableSelect, it provides its own workflows for generating custom values.

() => {
  const [value, setValue] = React.useState("");

  const options = [
    { label: "He/Him", value: "he/him" },
    { label: "She/Her", value: "she/her" },
    { label: "They/Them", value: "they/them" },
  ];

  return (
    <Stack shouldFitContainer>
      <p>Pronoun:</p>
      <Select
        name="pronoun"
        id="pronoun"
        options={options}
        valueRenderType="typeahead"
        value={value}
        onChange={(selectedOption) => setValue(selectedOption.label)}
        onInputChange={(newValue) => setValue(newValue)}
      />
    </Stack>
  );
};

Forwarding refs

Select accepts forwarded refs should you need to pass one, and inputRef.current returns all Select states, props etc. To more specifically target the html element, call inputRef.current.controlRef

() => {
  const selectRef = React.useRef();

  React.useEffect(() => {
    console.log(selectRef.current);
  }, []);

  return (
    <DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
      <label htmlFor="foodAllergies">Food allergies</label>
      <Select
        inputRef={selectRef}
        name="foodAllergies"
        id="foodAllergies"
        onChange={() =>
          (selectRef.current.controlRef.style.background = "hsl(187, 54%, 90%)")
        }
        options={[
          { label: "Nuts", value: "nuts" },
          { label: "Dairy", value: "dairy" },
          { label: "Wheat", value: "wheat" },
          { label: "Soy", value: "soy" },
          { label: "Shellfish", value: "shellfish" },
          { label: "Fish", value: "fish" },
          { label: "Egg", value: "egg" },
        ]}
      />
    </DocsFlex>
  );
};

Creatable Select

This component allows users to add options separate from the provided list. The new option will not appear in the list, but the select will accept the value. The ability to search options does not change, and all props are identical to <Select />.

The created value returns the same object as the data passed via options. (ie. an object with label and value)

If you pass a value string that doesn't correspond to an option provided in the options array, a <CreatableSelect /> will display an item with a label equal to the value provided.

import { CreatableSelect } from "@vitality-ds/components";
<DocsFlex css={{ flexGrow: 1, flexDirection: "column" }}>
  <label htmlFor="city">City</label>
  <CreatableSelect
    onChange={(newValue) => console.log(newValue)}
    name="city"
    id="city"
    options={[
      { label: "Brisbane", value: "brisbane" },
      { label: "Melbourne", value: "melbourne" },
      { label: "Sydney", value: "sydney" },
      { label: "Perth", value: "perth" },
      { label: "Darwin", value: "darwin" },
      { label: "Adelaide", value: "adelaide" },
      {
        label: "Canberra",
        value: "canberra",
      },
      { label: "Hobart", value: "hobart" },
    ]}
  />
</DocsFlex>

Async Select

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

AsyncSelect enables the list of options in the select menu to be asynchronously loaded based on input text. This is useful in the case of large sets of data, like lists of medication or names for example, where it might be impractical to load all options at once. If default options are required to load before any text is input, pass the data to the suggestedItems prop in the same way that options is structured for Select. The AsyncSelect will also display a loading state whilst waiting for results to load, and display 'No options' when nothing is returned.

Making the async request

The actual function that will fetch the list of options to render should be passed to the onSearch prop. onSearch can take a promise or a callback, and should return data in the same structure as the options array.

() => {
  const [error, setError] = React.useState(false);

  const selectProps = {
    name: "Name",
    id: "id",
    onChange: () => {},
  };

  const patientNames = [
    { label: "John Campbell", value: "johnCampbell" },
    { label: "Jonathan Cash", value: "jonathanCash" },
    { label: "John Hawthorne", value: "johnHawthorne" },
    { label: "John Smith", value: "johnSmith" },
    { label: "John Wilson", value: "johnWilson" },
    { label: "Johnny Wilson", value: "johnnyWilson" },
  ];

  function fetchPatients(inputValue) {
    return patientNames.filter((name) =>
      name.label.toLowerCase().includes(inputValue.toLowerCase())
    );
  }

  const loadOptions = (inputValue) =>
    new Promise((resolve, reject) => {
      resolve(fetchPatients(inputValue));
    });

  const failLoadOptions = (inputValue) =>
    new Promise((resolve, reject) => {
      reject(setError(true));
    });

  return (
    <DocsFlex css={{ flexDirection: "column", width: "100%" }} spacing="sm">
      <p>Called with promise that resolves</p>
      <AsyncSelect onSearch={loadOptions} {...selectProps} />
      <p>Promise that catches an error</p>
      {error && (
        <p style={{ color: "var(--vitality-colors-red9)" }}>
          Something went wrong
        </p>
      )}
      <AsyncSelect onSearch={failLoadOptions} {...selectProps} />
    </DocsFlex>
  );
};

Delaying request events

By default, AsyncSelect has a 250 millisecond delay to handle requests efficiently. This is based on community support for this as a benchmark for average human reaction period, but keep in mind more expensive requests might want a debounce delay that reflects the expected return time or limitations of the service being accessed. Consider what you know the capability of the service you're trying to access to be as well - you may only be able to handle 10 requests per second as an example. In this scenario, consider the number of active users at once as well to reach an appropriate delay period. Should you want to delay your request more, less, or to opt out of delayed requests altogether, use the requestDelayMs prop. AsyncSelect also accepts a minimum characters prop. This defaults to 0 but should you wish to only make a call after typing 3 characters for example, pass the number to minChars.

() => {
  function fetchPatients(inputValue) {
    const patientNames = [
      { label: "John Campbell", value: "johnCampbell" },
      { label: "Jonathan Cash", value: "jonathanCash" },
      { label: "John Hawthorne", value: "johnHawthorne" },
      { label: "John Smith", value: "johnSmith" },
      { label: "John Wilson", value: "johnWilson" },
      { label: "Johnny Wilson", value: "johnnyWilson" },
    ];
    return patientNames.filter((name) =>
      name.label.toLowerCase().includes(inputValue.toLowerCase())
    );
  }

  async function loadOptions(inputValue) {
    const result = await fetchPatients(inputValue);
    return result;
  }

  const asyncSelectProps = {
    name: "name",
    id: "name",
    onChange: () => {},
  };

  return (
    <DocsFlex css={{ flexDirection: "column", width: "100%" }} spacing="sm">
      <p>No delay</p>
      <AsyncSelect
        onSearch={loadOptions}
        requestDelayMs={0}
        {...asyncSelectProps}
      />
      <p>2 second delay</p>
      <AsyncSelect
        onSearch={loadOptions}
        requestDelayMs={2000}
        {...asyncSelectProps}
      />
      <p>Call after 3 characters input</p>
      <AsyncSelect onSearch={loadOptions} minChars={3} {...asyncSelectProps} />
    </DocsFlex>
  );
};

Loading state

While async requests are being handled, the loading state is managed by AsyncSelect automatically. An animated spinner will render within the input bar, and the menu will open with a 'Loading...' message. Should you need to specifically manage it however, set isLoading to true or false. AsyncSelect also handles a when no options are returned from the call with a 'No options' message.

Select...
Loading...
Select...
No options

Figma Library

Figma.logo

Props

Select Props

disabled

Description

Determines if the select input is enabled or not.

Type

boolean

hasError

Description

Boolean that indicates validation error.

Type

boolean

hideDropdownIndicator

Description

Hides the drop down indicator

Type

boolean

idRequired

Description

Id that pairs the input with the label

Type

string

inputId

Description

Id for the inner nested input element

Type

string

inputValue

Description

Controls the current value of the input field

Type

string

isClearable

Description

Boolean that determines if the input can be cleared once a value has been selected.

Type

boolean

Default Value

false

isMulti

Description

Determines if more than one item can be selected from the select.

Type

boolean

isSearchable

Description

Boolean value that allows users to type into the input to search for a value

Type

boolean

menuPlacement

Description

MenuPlacement Provides control of the placement of the menu in relation to the select input. valid options are "auto" (default), "top" and "bottom"

Type

"auto" | "bottom" | "top"

Default Value

auto

menuPosition

Description

MenuPosition Provides control of the position css property of the menu. This impacts layering and changes some placement behaviour, try to find which example is best for your use-case. Valid options are "absolute" (default) or "fixed". if the menu is portalled this prop is ignored and the position is set to "absolute" as absolute position in required to place correctly when portalled

Type

"absolute" | "fixed"

Default Value

absolute

nameRequired

Description

Name of the select input, should semantically reflect what the values are within - for eg. state for QLD, NSW etc.

Type

string

noOptionsMessage

Description

Function that defines the component rendered when no options are available

Type

() => ReactNode

onBlur

Description

Allows functions to run when component focus blurred

Type

(event: FocusEvent<HTMLInputElement, Element>) => void

onChangeRequired

Description

Function that handles change events on the select input.

Type

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

onInputChange

Description

Function that handles change events on the select input.

Type

(newValue: string) => void

openMenuOnClick

Description

Allows control of whether the menu is opened when the Select is clicked

Type

boolean

Default Value

true

optionsRequired

Description

The data used in the Select's dropdown for the Chip format The data used in the Select's dropdown

Type

OptionsOrGroups<BaseOption & Omit<ChipProps, "chipCheckboxProps"> & { chipCheckboxProps?: ChipCheckboxType & { onClick?: (thisItem: BaseOption) => void; }; }, GroupBase<BaseOption & Omit<ChipProps, "chipCheckboxProps"> & { chipCheckboxProps?: ChipCheckboxType & { onClick?: (thisItem: BaseOption) => void; }; }>> | OptionsOrGroups<BaseOption, GroupBase<BaseOption>>

placeholder

Description

change the placeholder text

Type

string

portalMenu

Description

Dictates weather the menu should be portalled to the document body. This is useful when you don't want the menu to potentially cause the page to shift or you specifically don't want the portal in document flow. It will always portal to document.body, which can cause scrolling issues if the input is inside a scrollable element and the menu is not set to menuPlacement: "auto".

Type

boolean

renderOption

Description

Allows control over how the options list renders.

Type

RenderOptionType

value

Description

The Select component's stored value of selected item(s) The Select component's stored value of selected item

Type

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

valueRenderType

Description

Determines custom formatting to enable 'typeahead' select to act more as a TextInput then a select

Type

"default" | "typeahead"

CreatableSelect Props

disabled

Description

Determines if the select input is enabled or not.

Type

boolean

hasError

Description

Boolean that indicates validation error.

Type

boolean

hideDropdownIndicator

Description

Hides the drop down indicator

Type

boolean

idRequired

Description

Id that pairs the input with the label

Type

string

inputId

Description

Id for the inner nested input element

Type

string

inputValue

Description

Controls the current value of the input field

Type

string

isClearable

Description

Boolean that determines if the input can be cleared once a value has been selected.

Type

boolean

Default Value

false

isMulti

Description

Determines if more than one item can be selected from the select.

Type

boolean

isSearchable

Description

Boolean value that allows users to type into the input to search for a value

Type

boolean

menuPlacement

Description

MenuPlacement Provides control of the placement of the menu in relation to the select input. valid options are "auto" (default), "top" and "bottom"

Type

"auto" | "bottom" | "top"

Default Value

auto

menuPosition

Description

MenuPosition Provides control of the position css property of the menu. This impacts layering and changes some placement behaviour, try to find which example is best for your use-case. Valid options are "absolute" (default) or "fixed". if the menu is portalled this prop is ignored and the position is set to "absolute" as absolute position in required to place correctly when portalled

Type

"absolute" | "fixed"

Default Value

absolute

nameRequired

Description

Name of the select input, should semantically reflect what the values are within - for eg. state for QLD, NSW etc.

Type

string

noOptionsMessage

Description

Function that defines the component rendered when no options are available

Type

() => ReactNode

onBlur

Description

Allows functions to run when component focus blurred

Type

(event: FocusEvent<HTMLInputElement, Element>) => void

onChangeRequired

Description

Function that handles change events on the select input.

Type

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

onCreateOption

Description

The function to perform when an option is created

Type

() => void

onInputChange

Description

Function that handles change events on the select input.

Type

(newValue: string) => void

openMenuOnClick

Description

Allows control of whether the menu is opened when the Select is clicked

Type

boolean

Default Value

true

optionsRequired

Description

The data used in the Select's dropdown for the Chip format The data used in the Select's dropdown

Type

OptionsOrGroups<BaseOption & Omit<ChipProps, "chipCheckboxProps"> & { chipCheckboxProps?: ChipCheckboxType & { onClick?: (thisItem: BaseOption) => void; }; }, GroupBase<BaseOption & Omit<ChipProps, "chipCheckboxProps"> & { chipCheckboxProps?: ChipCheckboxType & { onClick?: (thisItem: BaseOption) => void; }; }>> | OptionsOrGroups<BaseOption, GroupBase<BaseOption>>

placeholder

Description

change the placeholder text

Type

string

portalMenu

Description

Dictates weather the menu should be portalled to the document body. This is useful when you don't want the menu to potentially cause the page to shift or you specifically don't want the portal in document flow. It will always portal to document.body, which can cause scrolling issues if the input is inside a scrollable element and the menu is not set to menuPlacement: "auto".

Type

boolean

renderOption

Description

Allows control over how the options list renders.

Type

RenderOptionType

value

Description

The Select component's stored value of selected item(s) The Select component's stored value of selected item

Type

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

valueRenderType

Description

Determines custom formatting to enable 'typeahead' select to act more as a TextInput then a select

Type

"default" | "typeahead"

AsyncSelect Props

disabled

Description

Determines if the select input is enabled or not.

Type

boolean

hasError

Description

Boolean that indicates validation error.

Type

boolean

hideDropdownIndicator

Description

Hides the drop down indicator

Type

boolean

idRequired

Description

Id that pairs the input with the label

Type

string

inputId

Description

Id for the inner nested input element

Type

string

inputValue

Description

Controls the current value of the input field

Type

string

isClearable

Description

Boolean that determines if the input can be cleared once a value has been selected.

Type

boolean

Default Value

false

isMulti

Description

Determines if more than one item can be selected from the select.

Type

boolean

isSearchable

Description

Boolean value that allows users to type into the input to search for a value

Type

boolean

menuPlacement

Description

MenuPlacement Provides control of the placement of the menu in relation to the select input. valid options are "auto" (default), "top" and "bottom"

Type

"auto" | "bottom" | "top"

Default Value

auto

menuPosition

Description

MenuPosition Provides control of the position css property of the menu. This impacts layering and changes some placement behaviour, try to find which example is best for your use-case. Valid options are "absolute" (default) or "fixed". if the menu is portalled this prop is ignored and the position is set to "absolute" as absolute position in required to place correctly when portalled

Type

"absolute" | "fixed"

Default Value

absolute

minChars

Description

Minimum characters needed to be input before an asynchronous call is made

Type

number

0
nameRequired

Description

Name of the select input, should semantically reflect what the values are within - for eg. state for QLD, NSW etc.

Type

string

noOptionsMessage

Description

Function that defines the component rendered when no options are available

Type

() => ReactNode

onBlur

Description

Allows functions to run when component focus blurred

Type

(event: FocusEvent<HTMLInputElement, Element>) => void

onChangeRequired

Description

Function that handles change events on the select input.

Type

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

onInputChange

Description

Function that handles change events on the select input.

Type

(newValue: string) => void

Default Value

() => null

onSearchRequired

Description

The function that loads the options asynchronously

Type

(searchText: string) => void | Promise<void>

placeholder

Description

change the placeholder text

Type

string

portalMenu

Description

Dictates weather the menu should be portalled to the document body. This is useful when you don't want the menu to potentially cause the page to shift or you specifically don't want the portal in document flow. It will always portal to document.body, which can cause scrolling issues if the input is inside a scrollable element and the menu is not set to menuPlacement: "auto".

Type

boolean

renderOption

Description

Allows control over how the options list renders.

Type

RenderOptionType

requestDelayMs

Description

Number of milliseconds delay between typing into the input and the onSearch function being called.

Type

number

Default Value

250

suggestedItems

Description

Default list of options to load before requested are made. Structure is an array of objects, each object has a label and value.

Type

OptionsOrGroups<Option, GroupBase<Option>>

value

Description

The Select component's stored value of selected item(s) The Select component's stored value of selected item

Type

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

valueRenderType

Description

Determines custom formatting to enable 'typeahead' select to act more as a TextInput then a select

Type

"default" | "typeahead"

Default Value

default

© 2025