Skip to content
React NativeBeta

Select & MultiSelect

Pick one or many from a list, searchable and virtualised. A bottom sheet on phones, a popover on tablets.

Beta. React Native support is in beta. APIs may change or break between prereleases (npm tag native).

On this page
Web preview — real device may differ
tsx
import { useMemo, useState } from "react";
import { Text } from "react-native";
import { MultiSelect, Select, type ComboboxOption } from "@/component-lib/combobox";
import { useKitTheme } from "@/component-lib/shared/native";
import { Section } from "./layout"; // Section and Row: page layout for the demo
 
const STATES: ComboboxOption[] = [
  ["KL", "Kerala", "South"],
  ["TN", "Tamil Nadu", "South"],
  ["KA", "Karnataka", "South"],
  ["AP", "Andhra Pradesh", "South"],
  ["TG", "Telangana", "South"],
  ["MH", "Maharashtra", "West"],
  ["GJ", "Gujarat", "West"],
  ["GA", "Goa", "West"],
  ["RJ", "Rajasthan", "North"],
  ["PB", "Punjab", "North"],
  ["UP", "Uttar Pradesh", "North"],
  ["DL", "Delhi", "North"],
  ["WB", "West Bengal", "East"],
  ["OD", "Odisha", "East"],
  ["AS", "Assam", "North-east"],
  ["MN", "Manipur", "North-east"],
].map(([value, label, group]) => ({ value, label, group, keywords: [value] }));
 
const SKILLS: ComboboxOption[] = [
  "React", "React Native", "TypeScript", "Node.js", "GraphQL", "PostgreSQL", "Figma", "Kotlin", "Swift", "Go",
].map((label) => ({ value: label.toLowerCase(), label }));
 
export function ComboboxDemo() {
  const { tokens } = useKitTheme();
  const [state, setState] = useState<string | null>("KL");
  const [skills, setSkills] = useState<string[]>(["react", "typescript"]);
  const [extra, setExtra] = useState<ComboboxOption[]>([]);
  const [account, setAccount] = useState<string | null>(null);
 
  const many = useMemo<ComboboxOption[]>(
    () =>
      Array.from({ length: 500 }, (_, i) => ({
        value: `acc-${i + 1}`,
        label: `Account ${String(i + 1).padStart(3, "0")}`,
        description: i % 7 === 0 ? "Dormant since 2024" : `Ledger ${(i % 12) + 1}`,
        disabled: i % 41 === 40,
      })),
    [],
  );
 
  return (
    <>
      <Section title="Select · grouped, searchable">
        <Select
          label="State"
          options={STATES}
          value={state}
          onChange={(value) => setState(value)}
          description="Search by name or code (e.g. “KL”)."
        />
        <Text style={{ color: tokens.muted }}>Value: {String(state)}</Text>
      </Section>
 
      <Section title="MultiSelect · tags, max, create">
        <MultiSelect
          label="Skills"
          options={[...SKILLS, ...extra]}
          value={skills}
          onChange={setSkills}
          max={5}
          maxVisibleTags={3}
          allowCreate
          onCreate={(label) => {
            const option = { value: label.toLowerCase(), label };
            setExtra((list) => [...list, option]);
            setSkills((list) => (list.length < 5 ? [...list, option.value] : list));
          }}
        />
      </Section>
 
      <Section title="Select · 500 options, virtualised">
        <Select
          label="Ledger account"
          options={many}
          value={account}
          onChange={setAccount}
          placeholder="Pick an account"
        />
      </Section>
 
      <Section title="Select · error and disabled">
        <Select label="Branch" options={STATES} required error="Choose a branch to continue." />
        <Select label="Region" options={STATES} defaultValue="TN" disabled />
        <MultiSelect label="Tags" options={SKILLS} searchable={false} size="sm" placeholder="No search box" />
      </Section>
    </>
  );
}

Install

npx @grampro/kit@native add Select

Usage

tsx
import { Select, MultiSelect } from "@/component-lib/combobox";
 
<Select
  label="State"
  options={[
    { value: "KL", label: "Kerala" },
    { value: "TN", label: "Tamil Nadu" },
  ]}
  onChange={setState}
/>

Props

SelectProps

PropTypeDescription
valueV | null
defaultValueV | null
onChange(value: NoInfer<V> | null, option: ComboboxOption<NoInfer<V>> | null) => voidThe chosen value and its option; both null once cleared.
closeOnSelectbooleanClose the list after choosing. Default true.
options (required)readonly ComboboxOption<V>[]
mode"client" | "server"client filters options locally; server expects options to be the current results.
loadingbooleanShows a loading row; keeps current options visible.
onSearchChange(search: string) => voidCalled with the search text (debounced in server mode), and on open.
searchDebouncenumberDebounce for onSearchChange in server mode. Default 250 ms.
searchablebooleanShow the search box. Default true.
hasMorebooleanMore results are available; the list calls onLoadMore near its end.
onLoadMore() => void
filterFn(option: ComboboxOption<NoInfer<V>>, search: string) => boolean
renderOption(option: ComboboxOption<NoInfer<V>>, state: OptionState) => ReactNode
allowCreatebooleanOffers "Create …" when the search matches no option.
onCreate(label: string) => void
labelReactNode
placeholderstring
descriptionReactNode
errorReactNode
requiredboolean
disabledboolean
clearablebooleanShow the clear button. Default true.
sizeComboboxSizeDefault md.
maxHeightnumberMax height of the list in the tablet popover, in dp. Default 280. A phone sheet uses the screen.
presentation"auto" | "sheet" | "popover"auto (default): bottom sheet on a phone, popover on a tablet.
emptyMessageReactNodeShown when nothing matches the search. Defaults to the locale text.
styleStyleable<ViewStyle>
stylesComboboxStyles
localeTextPartial<ComboboxLocaleText>
onOpenChange(open: boolean) => void

MultiSelectProps

PropTypeDescription
valueV[]
defaultValueV[]
onChange(values: NoInfer<V>[], options: ComboboxOption<NoInfer<V>>[]) => void
maxnumberMaximum number of selections.
maxVisibleTagsnumberTags shown before collapsing into "+N more". Default 3.
showSelectAllbooleanShow "Select all" / "Clear all" under the list. Default true.
closeOnSelectbooleanClose the list after each choice. Default false.
options (required)readonly ComboboxOption<V>[]
mode"client" | "server"client filters options locally; server expects options to be the current results.
loadingbooleanShows a loading row; keeps current options visible.
onSearchChange(search: string) => voidCalled with the search text (debounced in server mode), and on open.
searchDebouncenumberDebounce for onSearchChange in server mode. Default 250 ms.
searchablebooleanShow the search box. Default true.
hasMorebooleanMore results are available; the list calls onLoadMore near its end.
onLoadMore() => void
filterFn(option: ComboboxOption<NoInfer<V>>, search: string) => boolean
renderOption(option: ComboboxOption<NoInfer<V>>, state: OptionState) => ReactNode
allowCreatebooleanOffers "Create …" when the search matches no option.
onCreate(label: string) => void
labelReactNode
placeholderstring
descriptionReactNode
errorReactNode
requiredboolean
disabledboolean
clearablebooleanShow the clear button. Default true.
sizeComboboxSizeDefault md.
maxHeightnumberMax height of the list in the tablet popover, in dp. Default 280. A phone sheet uses the screen.
presentation"auto" | "sheet" | "popover"auto (default): bottom sheet on a phone, popover on a tablet.
emptyMessageReactNodeShown when nothing matches the search. Defaults to the locale text.
styleStyleable<ViewStyle>
stylesComboboxStyles
localeTextPartial<ComboboxLocaleText>
onOpenChange(open: boolean) => void

Native and web

  • presentation: "auto" (sheet below 600dp, popover above), "sheet" or "popover".
  • Change events are onChange, as on the web. No keyboard navigation or type-ahead on touch.
  • Web version: Select & MultiSelect on the web.