Avara UI

Select

react-select is what people usually mean when they ask for "Select2 in React": searchable, multi-select, async loading, creatable options, grouped menus, and a combobox that is keyboard- and screen-reader-correct out of the box.
Algorize UI ships its own Select for the ordinary case. Reach for react-select when you need the search, the async loading or the tag input — and then make it look like everything else.

Unstyled, then repainted

The whole integration is two props. unstyled switches off every emotion style react-select injects, leaving a fully accessible but visually blank combobox. classNames then paints each internal slot with Tailwind classes, so the control ends up matching Input exactly — with no CSS overrides and no specificity fights.

Single and multi, on design tokens

Type to filter, arrow keys to move, Enter to pick. The control, the menu, the option rows and the multi-value chips are all painted from the same tokens as the rest of the form.

Useunstyled
before anything else. Without it you are layering classes on top of emotion styles and losing to them.
classNames
over styles. Both exist; classNames keeps the styling in Tailwind where the rest of the app is, and each slot receives the live state (isFocused, isSelected, isDisabled).
components
swaps a single internal part — here Option, to add the hint line and the tick — while leaving the behaviour untouched.
inputId
is what a <label htmlFor> points at. id lands on the wrapper, not the input, so a label wired to it does nothing.

Installation

npm install react-select
This example also uses these Algorize UI components — install whichever your project does not already have:
npx shadcn@latest add @algorize-ui/card @algorize-ui/ui-badge

Two things that will bite

A boolean-typed classNames config widens onChange everywhere. react-select infers IsMulti from the isMulti prop, and a config annotated ClassNamesConfig<Option, boolean, ...> drags that boolean back into the component's own inference — so onChange starts handing you "one option or many" at every call site. Build the config from a small generic factory instead and instantiate it once per mode, as the snippet does.
unstyled is not quite total. A few functional emotion rules survive, and one of them sets display: block on the option element. Laying a row out with flex in the option classNames therefore does nothing. Put the layout one level in, inside your own wrapper element, rather than reaching for !flex.

Going further

Everything below is the same classNames config with a different entry point:
NeedImport
Options loaded from an API as you typeAsyncSelect from react-select/async
Let people add options that do not existCreatableSelect from react-select/creatable
BothAsyncCreatableSelect from react-select/async-creatable
Each takes the same unstyled, classNames and components props, so the styling above carries over unchanged — only the data source differs.
For a plain dropdown with no search and no tags, the built-in Select is smaller and needs no configuration.