A React select component with a searchable listbox menu - for country, timezone and status pickers.
Select playground
Install Select
1. New project? One command installs the package and wires everything - see Installation:
pnpm dlx zyncat-ui init2. Import the component - it loads its own stylesheet:
import { Select } from '@zyncat/ui/select';Usage
import { Select } from '@zyncat/ui/select'; <Select ariaLabel="Timezone" value={tz} onChange={setTz} options={TIMEZONES} searchable />Compose Select in your application. No Tailwind or external styling library is required; every value resolves from Zyncat UI's token vocabulary.
Select props
Select
SelectGroup
SelectOption
Frequently asked questions
Set the searchable prop - <Select searchable options={options} value={v} onChange={setV} /> pins a filter input above the list and narrows it as you type. The filter matches an option's label, its description and its searchText, searchPlaceholder changes the input placeholder, and an empty result reads "No matches for ...".
Both, in the roles ARIA gives them. The trigger is a button with role="combobox" carrying aria-haspopup="listbox", aria-expanded, aria-controls and aria-activedescendant, and the popover holds a role="listbox" whose rows are role="option" with aria-selected - so a screen reader announces the active option while the keyboard stays on the list, or on the filter input when searchable is on.
You cannot restyle a native <select> menu because the browser draws it, so this renders its own trigger and a portalled listbox you style like any other element. size takes sm, md or lg, highlight switches the travelling highlight between the neutral and accent wash, rail adds an accent bar on its leading edge, and htmlProps forwards className, style and data-* to the root.
Pass value and onChange: onChange(value, option) fires on commit with the value string and the full SelectOption, so <Select value={tz} onChange={setTz} options={TIMEZONES} /> is a controlled select. Omit value and pass defaultValue to let it hold its own state; either way, committing closes the menu and returns focus to the trigger.
Yes. Each SelectOption takes an icon node, a description line under the label, disabled and searchText, and passing SelectGroup[] instead of a flat array renders labelled sections. leadingIcon pins your own icon on the trigger; without it the trigger shows the selected option's icon.
Arrow Down or Arrow Up opens it from the trigger; inside, the arrows, Home and End move the active option, Enter commits, Escape closes and returns focus, and Tab closes. With searchable off, Space commits and typing jumps to an option by prefix. It ships as compiled ESM with its 'use client' directive intact for the Next.js App Router, has zero runtime dependencies, and collapses every duration to 1ms under prefers-reduced-motion.