Skip to Content
Referencefusion-uiComponentsFormsSelect

Select

The Select component is a customizable dropdown input that allows users to select one or multiple options from a list. It supports features like search, custom option rendering, and integration with icons and other UI elements.


() => {
  const [value, setValue] = React.useState<string>();
  return (
    <Select
      constraints={{ maxWidth: 700 }}
      placeholder="Select a Fruit"
      options={[
        { value: "apple", title: "Apple" },
        { value: "banana", title: "Banana" },
        { value: "cherry", title: "Cherry" },
      ]}
      value={value}
      onChange={setValue}
    />
  );
}

Features

Multiple Selection

To enable multiple selection, set the multiple prop to true and manage an array of selected values:


() => {
  const [value, setValue] = React.useState<string[]>([]);
  return (
    <Select
      constraints={{ maxWidth: 700 }}
      placeholder="Select Fruits"
      options={[
        { value: "apple", title: "Apple" },
        { value: "banana", title: "Banana" },
        { value: "cherry", title: "Cherry" },
      ]}
      value={value}
      onChange={setValue}
      multiple
    />
  );
}

Leading and Trailing Elements

You can add leading and trailing elements to the Select component, such as an icon or a clear button.


() => {
  const [value, setValue] = React.useState<string>();
  return (
    <Select
      constraints={{ maxWidth: 700 }}
      placeholder="Select a City"
      leading={<Icon.Place opacity={500} />}
      options={[
        { value: "amsterdam", title: "Amsterdam" },
        { value: "paris", title: "Paris" },
        { value: "london", title: "London" },
      ]}
      value={value}
      onChange={setValue}
    />
  );
}

Detailed Options

You can provide additional attributes to each option, such as a subtitle and leading or trailing elements.

TypeScript
export interface Option { value: string; title?: Text; subtitle?: Text; leading?: ReactNode; trailing?: ReactNode; }

Below is a simple example with subtitles and leading elements.


() => {
  const [value, setValue] = React.useState<string>();
  return (
    <Select
      constraints={{ maxWidth: 700 }}
      placeholder="Select a Celestial Body"
      options={[
        {
          value: "star",
          title: "Star",
          subtitle: "Luminous sphere of plasma.",
          trailing: <Icon.LightMode />,
        },
        {
          value: "planet",
          title: "Planet",
          subtitle: "Chunk of matter orbiting a star.",
          trailing: <Icon.Public />,
        },
        {
          value: "moon",
          title: "Moon",
          subtitle: "Natural satellite of a planet.",
          trailing: <Icon.DarkMode />,
        },
      ]}
      value={value}
      onChange={setValue}
    />
  );
}

Custom Options

You can also add your own attributes to your options. Although the default Option component will not make use of these, you can provide your own implementation of the Option component to display the additional information or customize the layout of each option.

The example below uses the original Option component, but changes how its subtitle and trailing elements are displayed using some custom Option attributes.


() => {
  const [value, setValue] = React.useState<string>();
  return (
    <Select
      constraints={{ maxWidth: 700 }}
      placeholder="Select an Airport"
      options={[
        { value: "AMS", title: "Schiphol", city: "Amsterdam", country: "Netherlands" },
        { value: "LCH", title: "Heathrow", city: "London", country: "United Kingdom" },
        { value: "CDG", title: "Charles de Gaulle", city: "Paris", country: "France" },
      ]}
      Option={({ city, country, ...props }) => (
        <Option
          {...props}
          subtitle={`${city}, ${country}`}
          trailing={<Chip label={props.value} />}
        />
      )}
      value={value}
      onChange={setValue}
    />
  );
}

The component you pass to theOption prop automatically receives all of the custom attributes you provide to your options as props.

With Type Declaration

For extra type-safety, it is recommended to create an interface for your custom option. If you follow the steps below, you will have a robust and sustainable solution.

First, define your custom option interface extending Option.

TypeScript
interface AirportOption extends Omit<Option, "subtitle"> { city: string; country: string; }

Then, declare your custom options using the custom interface.

TypeScript
const airportOptions: AirportOption[] = [ { value: "AMS", title: "Schiphol", city: "Amsterdam", country: "Netherlands", }, { value: "CDG", title: "Charles de Gaulle", city: "Paris", country: "France", }, ];

Next, create a custom Option component that accepts the custom option interface by providing a type argument to the OptionProps generic.

TypeScript
function AirportOption({ city, country, ...props }: OptionProps<AirportOption>) { return ( <Option {...props} subtitle={`${city}, ${country}`} trailing={<Chip label={props.value} />} /> ); }

Finally, provide the custom options and your Option component to the Select component.

TypeScript
<Select placeholder="Select an Airport" options={airportOptions} value={value} onChange={setValue} Option={AirportOption} />

Custom Indicators

Next to using custom attributes, another common use-case for overriding the Option component is to customize the indicator displayed for selected options.


() => {
  const [value, setValue] = React.useState<string>();
  return (
    <Select
      constraints={{ maxWidth: 700 }}
      placeholder="Select Favorites"
      options={[
        { value: "apple", title: "Apple" },
        { value: "banana", title: "Banana" },
        { value: "cherry", title: "Cherry" },
      ]}
      value={value}
      onChange={setValue}
      multiple
      Option={({ isSelected, ...props }) => (
        <Option
          {...props}
          trailing={isSelected ? <Icon.Favorite /> : <Icon.FavoriteBorder />}
        />
      )}
    />
  );
}

The Select component will automatically forward all of your custom option attributes to the Option component props in a type-safe manner.

To enable search functionality within the dropdown, set the search prop to true.


() => {
  const [value, setValue] = React.useState<string>();
  return (
    <Select
      constraints={{ maxWidth: 700 }}
      placeholder="Search and Select"
      options={[
        { value: "apple", title: "Apple" },
        { value: "banana", title: "Banana" },
        { value: "cherry", title: "Cherry" },
      ]}
      value={value}
      onChange={setValue}
      search
    />
  );
}

By default, the search query is matched against the option’s title and subtitle. The options are then sorted based on the score, with the most relevant option displayed first.

Search Score and Matches

The search functionality uses a scoring algorithm to rank the options based on the search query. Since the score represents a penalty, the lowest scoring option is the most relevant.

If you want to make use of the score and matches, you can enable their inclusion in the option props and use them in your custom option component.

TypeScript
<Select // ... search={{ includeScore: true, includeMatches: true, }} Option={({ score, matches, ...props }) => ( <Option {...props} title={<Highlight text={title} matches={matches} />} tailing={<Chip label={score.toFixed(2)} />} /> )} />

Custom Search Keys

By default, the search query is matched against the option’s title and subtitle. If you want to search against additional keys, regardless of whether those keys are displayed in the option, you can provide a custom search keys array.


() => {
  const [value, setValue] = React.useState<string>();
  return (
    <Select
      constraints={{ maxWidth: 700 }}
      placeholder="Search and Select"
      options={[
        { value: "AMS", title: "Schiphol", city: "Amsterdam", country: "Netherlands" },
        { value: "LCH", title: "Heathrow", city: "London", country: "United Kingdom" },
        { value: "CDG", title: "Charles de Gaulle", city: "Paris", country: "France" },
      ]}
      value={value}
      onChange={setValue}
      search={{
        keys: ["value", "title", "city", "country"],
      }}
    />
  );
}

It is also possible to provide weights for each key to adjust the search relevance.

TypeScript
<Select // ... search={{ keys: [ { name: "value", weight: 1 }, { name: "title", weight: 1 }, { name: "city", weight: 0.5 }, { name: "country", weight: 0.25 }, ], }} />

Anatomy

Apple
Select a Fruit
  • Apple
  • Banana
1.SelectThe select component.
2.ValueThe currently selected value.
3.PlaceholderThe placeholder text displayed when no value is selected.
4.IconThe icon indicating the state of the options popover.
5.OptionsThe options list displayed when the popover is open.

Reference

SelectProps

NameTypeRequiredDecription
optionsOption[]trueAn array of option objects.
valuestring | string[]trueThe current selected options.
onChange(value: string | string[]) => voidtrueCallback when selection changes.
placeholderstringfalsePlaceholder text when no option is selected.
multiplebooleanfalseEnables multiple selection when true.
searchboolean | FuseOptionsfalseEnables search within options when true.
leadingReactNodefalseElement displayed at the start of the select input.
trailingReactNodefalseElement displayed at the end of the select input.
OptionComponentType<OptionProps>falseCustom component for rendering each option.

Option

NameTypeRequiredDecription
valuestringtrueThe value of the option.
titleTextfalseDisplay text for the option.
subtitleTextfalseSecondary text for the option.
leadingReactNodefalseElement displayed at the start of the option.
trailingReactNodefalseElement displayed at the end of the option.

FuseOptions

Please refer to the Fuse.js Documentation .

Tokens

KeyDefault Value
row.base.colorinherit
row.base.backgroundColorvar(--_1i4ikbk10)
row.base.paddingLeftvar(--gpqiqd7)
row.base.paddingRightvar(--gpqiqd7)
row.base.paddingTopvar(--gpqiqd7)
row.base.paddingBottomvar(--gpqiqd7)
row.base.borderWidthvar(--gpqiqbf)
row.base.borderStylesolid
row.base.borderColorvar(--_1i4ikbkx)
row.base.borderRadiusvar(--gpqiqdv)
row.base.gapvar(--gpqiqd7)
row.base.:hover.borderColorvar(--_1i4ikbky)
row.base.:focus-within.borderColorvar(--_1i4ikbky)
row.base.:active.borderColorvar(--_1i4ikbky)
row.variants.error.true.backgroundColorvar(--_1i4ikbki)
row.variants.error.true.borderColorvar(--_1i4ikbkg)
row.variants.error.true.colorvar(--_1i4ikbkg)
row.variants.error.true.:hover.backgroundColorvar(--_1i4ikbki)
row.variants.error.true.:hover.borderColorvar(--_1i4ikbkg)
row.variants.error.true.:hover.colorvar(--_1i4ikbkg)
row.variants.error.true.:focus-visible.backgroundColorvar(--_1i4ikbki)
row.variants.error.true.:focus-visible.borderColorvar(--_1i4ikbkg)
row.variants.error.true.:focus-visible.colorvar(--_1i4ikbkg)
row.variants.error.true.:active.backgroundColorvar(--_1i4ikbki)
row.variants.error.true.:active.borderColorvar(--_1i4ikbkg)
row.variants.error.true.:active.colorvar(--_1i4ikbkg)
row.variants.error.true.:disabled.backgroundColorvar(--_1i4ikbki)
row.variants.error.true.:disabled.borderColorvar(--_1i4ikbkg)
row.variants.error.true.:disabled.colorvar(--_1i4ikbkg)
row.variants.disabled.true.opacityvar(--_1i4ikbk2i)
input.base.fontvar(--_1i4ikbk25)
input.base.paddingLeft0
input.base.paddingRight0
input.base.paddingTop0
input.base.paddingBottom0
popover.base.backgroundColorvar(--_1i4ikbk10)
popover.base.borderRadiusvar(--gpqiqdv)
popover.base.borderWidthvar(--gpqiqbf)
popover.base.borderStylesolid
popover.base.borderColorvar(--_1i4ikbkx)
option.base.paddingLeftvar(--gpqiqd7)
option.base.paddingTopvar(--gpqiqd7)
option.base.paddingRightvar(--gpqiqd7)
option.base.paddingBottomvar(--gpqiqd7)
option.base.borderRadius0
option.base.backgroundColorvar(--_1i4ikbk10)
option.base.colorvar(--_1i4ikbkv)
option.base.gapvar(--gpqiqd7)
option.base.:hover.backgroundColorcolor-mix(in srgb, transparent 95%, var(--_1i4ikbkv) 5%)
option.base.:hover.colorvar(--_1i4ikbkv)
option.base.:focus-visible.backgroundColorcolor-mix(in srgb, transparent 95%, var(--_1i4ikbkv) 5%)
option.base.:focus-visible.colorvar(--_1i4ikbkv)
option.base.:active.backgroundColorcolor-mix(in srgb, transparent 95%, var(--_1i4ikbkv) 5%)
option.base.:active.colorvar(--_1i4ikbkv)
Last updated on