# 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](https://react-select.com/) 5 and styled to match the other Bootstrap form fields. `Select` is part of the PRO version of [AdminKit for Next.js](/nextjs/).

`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 class="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>`:

```html
<label class="form-label" for="nativeSelect">Status</label>
<select class="form-select" id="nativeSelect">
  <option value="active" selected>Active</option>
  <option value="paused">Paused</option>
  <option value="archived">Archived</option>
</select>
```
```tsx code
<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](/pricing/#nextjs).

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`.

```html
<label class="form-label" id="stateLabel" for="stateSelect">State</label>
<select class="form-control" id="stateSelect">
  <optgroup label="Mountain Time Zone">
    <option value="AZ" selected>Arizona</option>
    <option value="CO">Colorado</option>
    <option value="UT">Utah</option>
  </optgroup>
  <optgroup label="Pacific Time Zone">
    <option value="CA">California</option>
    <option value="OR">Oregon</option>
    <option value="WA">Washington</option>
  </optgroup>
</select>

<script>
new Choices(document.querySelector("#stateSelect"));
</script>
```
```tsx code title="src/app/(dashboard)/locations/page.tsx"
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:

1. On the server and during hydration, while `useMounted()` is false, it renders a copy of the closed control with the same `react-select__` classes. The copy shows the selected value or values from `value` or `defaultValue`, or the placeholder (`"Select..."` by default). It gets `react-select--is-disabled` when `isDisabled` is set.
2. Once the component has mounted in the browser, React Select replaces the copy. `instanceId` comes from `useId()`, 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](https://react-select.com/props). 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=""`.

```html
<label class="form-label" for="statesSelect">States</label>
<select class="form-control" id="statesSelect" multiple>
  <option value="AZ">Arizona</option>
  <option value="CA">California</option>
  <option value="CO">Colorado</option>
  <option value="OR">Oregon</option>
  <option value="UT">Utah</option>
  <option value="WA">Washington</option>
</select>

<script>
new Choices(document.querySelector("#statesSelect"), { removeItemButton: true });
</script>
```
```tsx code
<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.

```tsx title="src/components/states-filter.tsx"
"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-labelledby` and the `id` of a visible label, or with `inputId` and 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](/react/docs/forms/select/): the same select in AdminKit for React (Vite)
- [Bootstrap 5 select](/bootstrap/docs/forms/select/) and [multiselect](/bootstrap/docs/forms/multiselect/): the select of the HTML version
- [Form controls](/nextjs/docs/forms/form-control/): inputs, textareas and submitting a form
- [Advanced Inputs demo](https://nextjs-demo.adminkit.io/forms-advanced-inputs)

---

Canonical HTML page: https://adminkit.io/nextjs/docs/forms/select/

Site overview for agents: https://adminkit.io/llms.txt (expanded: https://adminkit.io/llms-full.txt) · Documentation: https://adminkit.io/bootstrap/docs/ · Questions: support@adminkit.io
