Next.js React Select
Next.js React Select in AdminKit PRO: a searchable, Bootstrap-styled select with groups and multiple values that renders cleanly on the server.
Use Select when a plain dropdown isn't enough: people can search as they type, pick several values and browse grouped options. It is built on React Select 5 and styled to match the other Bootstrap form fields. Select is part of the PRO version of AdminKit for Next.js.
Select is a client component: its file starts with "use client", because the search and the menu run in the browser. Your page can still render it, as the next sections show.
For a short, fixed list, the native <select className="form-select"> is the simpler choice. It is plain markup, so it works in a server component, and it is in the free and PRO versions.
Example
Start with the native select. Set the preselected option with defaultValue on the <select>:
<label className="form-label" htmlFor="nativeSelect">Status</label>
<select className="form-select" id="nativeSelect" defaultValue="active">
<option value="active">Active</option>
<option value="paused">Paused</option>
<option value="archived">Archived</option>
</select>form-select-lg, form-select-sm, multiple and disabled work as in Bootstrap.
React Select
This component is only available in AdminKit PRO. Learn more.
Your page can stay a server component and render Select directly, as long as every prop is plain data. The Advanced Inputs page of the demo works this way. options takes a list of { value, label } objects, or groups of them, typed as SelectOption and SelectGroup.
import { Select, type SelectGroup } from "@/components/plugins/select";
import { pageMetadata } from "@/lib/site";
export const metadata = pageMetadata("/locations");
const states: SelectGroup[] = [
{
label: "Mountain Time Zone",
options: [
{ value: "AZ", label: "Arizona" },
{ value: "CO", label: "Colorado" },
{ value: "UT", label: "Utah" },
],
},
{
label: "Pacific Time Zone",
options: [
{ value: "CA", label: "California" },
{ value: "OR", label: "Oregon" },
{ value: "WA", label: "Washington" },
],
},
];
export default function LocationsPage() {
return (
<>
<h1 className="h3 mb-3">Locations</h1>
<label className="form-label" id="state-label">State</label>
<Select
options={states}
defaultValue={states[0]?.options[0]}
aria-labelledby="state-label"
/>
</>
);
}List /locations in src/data/pages.json, where pageMetadata() reads the page's title and description. The preview shows the select of the HTML version. _react-select.scss gives React Select the same look.
Server render and hydration
Next.js renders the page's HTML on the server, and the browser then takes over that HTML. This takeover is called hydration, and it only works when the browser's first render matches the HTML. React Select adds its layout styles in the browser while it runs, so the server HTML could never match it. That is why Select renders in two steps:
- On the server and during hydration, while
useMounted()is false, it renders a copy of the closed control with the samereact-select__classes. The copy shows the selected value or values fromvalueordefaultValue, or the placeholder ("Select..."by default). It getsreact-select--is-disabledwhenisDisabledis set. - Once the component has mounted in the browser, React Select replaces the copy.
instanceIdcomes fromuseId(), so the ids React Select generates are stable and unique on the page.
The control has its final size from the first paint, and the server and browser output never disagree.
Props
Select accepts the props of React Select. AdminKit changes a few defaults, and a prop you pass overrides them:
| Prop | Default in AdminKit | Effect |
|---|---|---|
unstyled | true | React Select's own colors are off; the Sass styles the react-select__ classes |
classNamePrefix | "react-select" | The class prefix the Sass expects |
isClearable | false | No clear button |
closeMenuOnSelect | !isMulti | A multiple select stays open while you pick |
menuPlacement | "auto" | The menu opens upward when there is no room below |
noOptionsMessage | "No results found" after a search, "No choices to choose from" otherwise | |
instanceId | from useId() | Stable ids for the generated elements |
className goes to the container, with react-select and react-select--single or react-select--multi. A server component can only pass props that reach the browser as data, such as strings, arrays and plain objects. onChange, formatOptionLabel, getOptionLabel and other functions need a client component, as shown below.
The component is in src/components/plugins/select.tsx. It renders React Select unstyled, with the react-select__ class prefix, and src/scss/5-vendor/_react-select.scss gives it the Bootstrap look.
Multiple values
When people may pick several values, add isMulti. The values show as tags with a remove button. The demo passes placeholder="".
<label className="form-label" id="states-label">States</label>
<Select options={states} isMulti placeholder="" aria-labelledby="states-label" />Controlled value
When your code needs to read or set the selection, pass value and onChange from a client component. Give the select a name too: React Select then adds hidden inputs, so FormData in your submit handler includes the values. Send them to your API with fetch, because a static export has no Server Actions.
"use client";
import { useState } from "react";
import { Select, type SelectOption } from "@/components/plugins/select";
export function StatesFilter({ options }: { options: SelectOption[] }) {
const [selected, setSelected] = useState<readonly SelectOption[]>([]);
return (
<>
<label className="form-label" id="filter-label">States</label>
<Select
options={options}
isMulti
value={selected}
onChange={(values) => setSelected(values)}
name="states"
aria-labelledby="filter-label"
/>
</>
);
}options is plain data, so the server component page can pass it to StatesFilter.
Accessibility
- Name the select with
aria-labelledbyand theidof a visible label, or withinputIdand a<label htmlFor>, as the tags field of the Prompts app does. - Type to search, move with the arrow keys, pick with Enter and close with Escape.
- The server-rendered copy is not interactive: the select becomes usable once the page has hydrated.
Related
- React Select Bootstrap: the same select in AdminKit for React (Vite)
- Bootstrap 5 select and multiselect: the select of the HTML version
- Form controls: inputs, textareas and submitting a form
- Advanced Inputs demo
Last updated on
Form control
Next.js Bootstrap form controls: inputs and textareas as Bootstrap markup in server components, with state and submits handled in client components.
Checkbox and radio
Next.js Bootstrap checkbox, radio and switch markup: plain checks in server components, and toggle buttons that keep their state in a client component.