Single · Searchable · Clearable
The classic dropdown. Search to filter, click ✕ to clear.
<Select
options={options}
value={value}
onChange={setValue}
isClearable
showMenuSearchInput
placeholder="Pick a bundler…"
/>Accessible, SSR-safe, fully customizable. Single & multi-select, async loading, custom components — all in a tiny, tree-shakeable bundle.
npm install react-next-selectimport 'react-next-select/style.css'Hydration-friendly. Works seamlessly with Next.js App Router & Pages Router.
Full keyboard navigation, ARIA roles, and screen-reader announcements out of the box.
Style every part via the `styles` prop, or replace components entirely.
No heavy dependencies. Tree-shakeable ESM build for tiny bundles.
Every Select on this page updates instantly. Powered by a single CSS variable.
#a78bfargb(167, 139, 250):root {
--rns-accent: 167 139 250;
}💡 Scroll down — every Select below uses this color too.
color="purple" tints the background, border, hover glow, focus ring, selected option and chips, each at its own opacity. Per Select, no CSS needed.
"purple"variant — 10 stylessize<Select
options={options}
color="purple"
showCheckmark
/>Mix and match props — the live preview and snippet below update as you go.
menuPlacementplaceholdermenuSearchPlaceholdercolornot setvariantsizeradiusdefaultmaxSelectedoptionsdebounceMsbgColornot setborderColornot setfocusColornot settextColornot setplaceholderColornot setmenuBgColornot setoptionHoverColornot setoptionSelectedColornot setchipColornot set<Select
options={options}
value={value}
onChange={setValue}
isClearable
/>Every variant is fully interactive. Click around — keyboard works too.
The classic dropdown. Search to filter, click ✕ to clear.
<Select
options={options}
value={value}
onChange={setValue}
isClearable
showMenuSearchInput
placeholder="Pick a bundler…"
/>Pick many. Menu stays open while you keep selecting.
<Select
options={options}
value={value}
onChange={setValue}
isMulti
isClearable
closeMenuOnSelect={false}
/>A dedicated search field lives inside the menu — great for long lists.
<Select
options={options}
isMulti
showMenuSearchInput
menuSearchPlaceholder="Search options..."
/>Fetch options from anywhere — APIs, databases, anything Promise-based. debounceMs waits for a pause in typing, so you get one request instead of one per keystroke.
<Select
loadOptions={async (input) => {
const res = await fetch(`/api/search?q=${input}`)
return res.json()
}}
defaultOptions
debounceMs={300}
/>Swap in your own Option renderer. Show avatars, badges, anything.
<Select
options={options}
components={{ Option: MyOption }}
/>Swap the chevron without writing a full component. Pass a string, a ReactNode, or a function that receives { isOpen }. The 180° rotation animates automatically.
<Select
options={options}
dropdownIcon={
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="3"
strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 12 15 18 9" />
</svg>
}
/>Pass { label, options } items. Search filters inside each group and arrow keys flow across them.
const options = [
{ label: 'UI libraries', options: [
{ value: 'react', label: 'React' }, ...
]},
{ label: 'Bundlers', options: [...] },
]
<Select options={options} isMulti showCheckmark color="sky" />Set isDisabled on an option (or pass isOptionDisabled). It's greyed out, can't be clicked, and the keyboard skips it.
const options = [
{ value: 'free', label: 'Free' },
{ value: 'team', label: 'Team — coming soon', isDisabled: true },
...
]
<Select options={options} color="amber" variant="filled" />isInvalid switches to a danger border and ring and sets aria-invalid. Open and close without picking to see it.
<Select
options={countries}
value={country}
onChange={setCountry}
onMenuClose={() => setTouched(true)}
isInvalid={touched && !country}
/>Set individual parts with bgColor, borderColor, focusColor, textColor, menuBgColor, optionHoverColor, optionSelectedColor, chipColor and radius.
<Select
options={options}
color="orange" // hover, scrollbar, icons
bgColor="#fff7ed"
borderColor="#fdba74"
focusColor="#ea580c"
textColor="#7c2d12"
placeholderColor="#c2410c"
menuBgColor="#fff7ed"
optionHoverColor="#ffedd5"
optionSelectedColor="#fed7aa"
radius={8}
style={{ '--rns-muted': '#9a3412' }} // any --rns-* token
/>isCreatable offers “Create …” when the text matches nothing. Handle it with onCreateOption, or let the Select pick the new option itself.
<Select
options={tags}
value={value}
onChange={setValue}
isMulti
isCreatable
onCreateOption={(text) => {
const tag = { value: text, label: text }
setTags((t) => [...t, tag])
setValue((v) => [...v, tag])
}}
/>showSelectAll adds a Select all / Clear all row (it respects the search). maxSelected disables the rest once the limit is hit.
<Select
options={options}
isMulti
showSelectAll
maxSelected={5}
showCheckmark
/>Give an option an icon and a description and they render automatically. highlightMatch underlines what you typed.
const options = [
{ value: 'card', label: 'Credit card',
icon: '💳', description: 'Visa, Mastercard, Amex' },
...
]
<Select options={options} highlightMatch showCheckmark />Inside an overflow: hidden box (a modal, a table cell) the menu gets cut off. menuPortalTarget renders it into document.body instead.
<Select
options={options}
menuPortalTarget={document.body}
/>Drop it in with zero config — sensible defaults, accessible from day one.
<Select options={options} onChange={setValue} />What changed in each release — newest at the top.