# React Multi Select mit Suche, Chips und Bottom Sheet

> So baust du einen React Multi Select, der echte Daten aushält: Chips mit +N-Überlauf, Suche, Alle auswählen nur für Treffer, ein Limit und ein Bottom Sheet.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 16. Okt. 2026
- Kategorie: [Komponenten-Guides](https://wingo-ui.com/de/blog/category/components)
- Lesezeit: 10 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/react-multi-select
- Englische Fassung: https://wingo-ui.com/blog/react-multi-select

## Kurz gesagt

Ein guter React Multi Select öffnet eine Liste mit Checkbox-Zeilen, bleibt beim Auswählen offen, zeigt die Auswahl als Chips, die bei voller Zeile zu einem +N-Chip zusammenfallen, und bringt Suche, Alle auswählen und ein Maximum mit, wenn die Liste sie braucht. Auf dem Smartphone öffnet er ein Bottom Sheet mit großen Zeilen und einem Fertig-Button. Nimm ihn für mehrere Werte aus einer bekannten Liste, einen Tag Input, wenn Leute eigene Werte tippen, und einfache Checkboxen bei weniger als etwa sieben Optionen.

Ein React Multi Select fängt einfach an: ein Dropdown, eine Checkbox pro Zeile, ein Array im State. Dann kommen echte Daten. Jemand wählt neun Kategorien, und durch die Chips wächst das Feld auf drei Zeilen. Die Liste hat 140 Optionen, und niemand findet „Haustiere“. „Alle auswählen“ erwischt auch Zeilen, die die Suche ausgeblendet hatte. Auf dem Smartphone öffnet sich das Popover genau unter dem Daumen, der es angetippt hat. Dieser Guide zeigt, was eine React Multi Select Komponente können muss, welche Props im [Multi Select](https://wingo-ui.com/components/multi-select) von Wingo UI (der Bibliothek, die wir bauen) zählen, wie Tastatur und Screenreader funktionieren, wie das Layout auf dem Smartphone aussieht und wann ein [Tag Input](https://wingo-ui.com/components/tag-input), eine [Combobox](https://wingo-ui.com/components/combobox) oder einfache [Checkboxen](https://wingo-ui.com/components/checkbox) die bessere Wahl sind.

## Was muss ein React Multi Select können?

Ein React Multi Select muss sieben Dinge gut können. Wir haben unseren an dieser Liste geprüft, und sie taugt als Checkliste für jedes Multi Select Dropdown mit Checkbox und Suche:

- **Beim Auswählen offen bleiben.** Jede Zeile schaltet um, und die Liste bleibt offen, weil Leute meistens mehr als eine Option wählen.
- **Die Auswahl als Chips zeigen, die passen.** So viele Chips, wie in eine Zeile passen, dann ein „+N“-Chip, damit das Feld seine Höhe behält.
- **Entfernen, ohne zu öffnen.** Jeder Chip hat sein eigenes x, und Backspace entfernt den letzten.
- **Suchen, wenn die Liste lang ist.** Kurze Listen brauchen kein Suchfeld, lange sind ohne eins unbrauchbar.
- **Alle auswählen und ein Maximum.** Alle auswählen für „jede Region“, ein Limit für „bis zu 3 Städte“, beides in der Oberfläche erklärt.
- **Absenden wie ein natives Feld.** Die Werte landen in `FormData`, `required` blockiert ein leeres Absenden, ein Formular-Reset stellt den Standardwert wieder her.
- **Auf dem Smartphone funktionieren.** Große Zeilen, 16px große Schrift, damit iOS nicht zoomt, und eine Ebene, die der Daumen erreicht.

Installier den Multi Select und render ihn:

```bash
npx wingo-ui@latest add multi-select
```

```tsx
"use client";

import { useState } from "react";
import { Building2, Car, Dumbbell, Smartphone } from "lucide-react";
import { MultiSelect, type MultiSelectOption } from "@/components/ui/multi-select";

const CATEGORIES: MultiSelectOption[] = [
  { value: "real-estate", label: "Immobilien", icon: <Building2 /> },
  { value: "vehicles", label: "Fahrzeuge", icon: <Car /> },
  { value: "electronics", label: "Elektronik", icon: <Smartphone /> },
  { value: "sports", label: "Sport", icon: <Dumbbell /> },
];

export function FollowedCategories() {
  const [categories, setCategories] = useState<string[]>(["vehicles"]);
  return (
    <MultiSelect
      label="Abonnierte Kategorien"
      placeholder="Kategorien wählen"
      options={CATEGORIES}
      value={categories}
      onValueChange={setCategories}
      name="categories"
    />
  );
}
```

Das Icon einer Option erscheint in der Zeile und in ihrem Chip. So landen Avatare neben den Namen in einer Auswahl für Zuständige. Optionen können auch Gruppen sein: `{ label: "Fahrzeuge", items: [...] }`.

> Live-Demo (Multi Select): Öffne die Liste und hak Zeilen an, bis die Chips nicht mehr passen: Die übrigen fallen zu einem +N-Chip zusammen. Entferne einen Chip mit seinem x, ohne die Liste zu öffnen. Probier sie unter [Multi Select](https://wingo-ui.com/components/multi-select) aus und installier sie mit `npx wingo-ui@latest add multi-select`.

## Wie zeige ich +N, wenn Multi Select Chips überlaufen?

Miss die Chips: Render jeden Chip unsichtbar in einer versteckten Ebene, addiere ihre Breiten nacheinander und hör beim letzten Chip auf, der passt und noch Platz für den +N-Chip lässt. Die beiden naheliegenden Abkürzungen funktionieren nicht. Brechen die Chips in neue Zeilen um, wächst das Feld und schiebt das Formular nach unten. Eine feste Anzahl („zeig 2, dann +N“) verschenkt Platz in einem breiten Feld und läuft in einem schmalen mit langen Labels trotzdem über.

Genau dieses Messen macht `maxVisibleChips={0}` (der Standard). Ein `ResizeObserver` beobachtet das Feld und die versteckte Ebene. So passt sich die Anzahl an, wenn sich die Fenstergröße ändert oder eine Webfont spät lädt und die Labels breiter macht. Mindestens ein Chip ist immer zu sehen, notfalls gekürzt, damit das Feld nie nur „+5“ zeigt. Der +N-Chip trägt die versteckten Namen in seinem `title`, du siehst sie also, wenn du mit der Maus darüberfährst.

Für einen anderen Kompromiss reicht eine Prop:

| Prop | Ergebnis |
| --- | --- |
| `maxVisibleChips={0}` (Standard) | So viele Chips, wie in eine Zeile passen, bei Größenänderung neu gemessen, dann +N |
| `maxVisibleChips={2}` | Höchstens zwei Chips, dann +N |
| `wrap` | Chips laufen in weitere Zeilen, und das Feld wächst |
| `display="count"` | „3 selected“ statt Chips |
| `display="text"` | Die Namen mit Kommas verbunden und gekürzt |

`display="count"` passt zu Filterleisten. Dort ist das Feld schmal, und die einzelnen Werte zählen weniger als die Tatsache, dass ein Filter aktiv ist.

## Wie ergänze ich Suche und Alle auswählen im Multi Select?

Ergänze die Suche, sobald die Liste mehr als acht Optionen hat, Alle auswählen, wenn „jede Option“ eine echte Wahl ist, und ein Maximum, wenn dein Produkt ein Limit hat. Mit `search="auto"` (der Standard) erscheint die Suchzeile ab mehr als acht Optionen oder sobald du `loadOptions` oder `creatable` übergibst; `"always"` und `"never"` überschreiben das. Beim Tippen spielen Groß- und Kleinschreibung und Akzente keine Rolle („zurich“ findet „Zürich“), passende Buchstaben werden fett, und Backspace in der leeren Suche entfernt den letzten Chip. Für Listen, die auf einem Server liegen, bekommt `loadOptions` die Suchanfrage nach einem `debounce` von 250ms und ein `AbortSignal`. Die vorige Anfrage wird abgebrochen, damit eine alte Antwort nie eine neuere überschreibt:

```tsx
"use client";

import { useState } from "react";
import { MultiSelect, type MultiSelectItems } from "@/components/ui/multi-select";

export function TagPicker() {
  const [tags, setTags] = useState<string[]>([]);
  return (
    <MultiSelect
      label="Tags"
      options={[]}
      loadOptions={async (query, { signal }) => {
        const res = await fetch(`/api/tags?q=${encodeURIComponent(query)}`, { signal });
        return (await res.json()) as MultiSelectItems;
      }}
      minQueryLength={2}
      value={tags}
      onValueChange={setTags}
    />
  );
}
```

Die letzten 20 Suchanfragen werden gecacht. Tippst du zu einer früheren Anfrage zurück, erscheinen ihre Ergebnisse ohne neuen Request. Die Komponente merkt sich außerdem das Label jeder Option, die sie einmal gesehen hat. Gewählte Werte, die nicht in den aktuellen Ergebnissen stehen, behalten so ihre Chips mit echten Namen. Eine Suche nach „vin“ macht aus dem Chip „Verhandlungsbasis“, den du vorher gewählt hast, keine rohe ID.

`selectAll` fügt oben eine Zeile hinzu. Sie schaltet nur die sichtbaren Optionen um: Wer nach „neu“ gesucht und Alle auswählen gedrückt hat, meinte die passenden Zeilen. `maxSelected={3}` begrenzt die Auswahl, deaktiviert am Limit die nicht gewählten Zeilen, und die Fußzeile zeigt „2 of 3 selected“. Sind beide aktiv, stoppt Alle auswählen am Limit.

> Live-Demo (Multi Select): Gib in der Suchzeile etwas ein, um zu filtern, und nutz dann Alle auswählen: Es wählt nur die Zeilen, die nach der Suche noch zu sehen sind. Probier sie unter [Multi Select](https://wingo-ui.com/components/multi-select) aus und installier sie mit `npx wingo-ui@latest add multi-select`.

## Wie mache ich einen Multi Select barrierefrei?

Mach das Feld zu einer Combobox, die eine Listbox mit `aria-multiselectable` steuert, halte den DOM-Fokus aus der Liste heraus und lass die Anzahl vorlesen. Im Multi Select ist das Feld ein Button mit `role="combobox"`, `aria-haspopup="listbox"`, `aria-expanded` und `aria-activedescendant`, das auf die markierte Zeile zeigt. Ein Screenreader liest also die Zeile vor, die gerade per Tastatur markiert ist, während der Fokus auf dem Feld bleibt. Ist die Suchzeile sichtbar, wandert der Fokus hinein, und das Suchfeld trägt stattdessen `aria-activedescendant`. So kommst du auch beim Tippen mit den Pfeiltasten durch die Zeilen. Den Namen des Felds bilden sein Label und die aktuelle Zusammenfassung, ein Screenreader liest also „Abonnierte Kategorien, 3 selected“. Die Anzahl in der Fußzeile steht in einer höflichen Live-Region (`aria-live="polite"`), damit nach jedem Umschalten die neue Anzahl vorgelesen wird.

Das Tastaturmodell:

- **Geschlossen:** Enter, Leertaste oder Pfeil nach unten öffnen die Liste, Pfeil nach oben öffnet sie auf der letzten Zeile, Pos1 und Ende öffnen sie am jeweiligen Ende, Backspace entfernt den letzten Chip.
- **Offen:** Pfeil nach oben und unten bewegen die Markierung und springen vom Ende der Liste an ihren Anfang und umgekehrt, Bild auf und Bild ab springen in großen Schritten, Enter oder Leertaste schalten die markierte Zeile um und lassen die Liste offen, Cmd oder Strg+A wählt alles aus, solange die Liste (nicht die Suche) den Fokus hat, Escape oder Alt+Pfeil nach oben schließen und geben den Fokus ans Feld zurück, Tab schließt und geht weiter.

Die x-Buttons der Chips liegen mit Absicht nicht in der Tab-Reihenfolge. Durch neun Chips zu tabben, um zum nächsten Feld zu kommen, kostet neun Tastendrücke; Backspace auf dem Feld entfernt den letzten Chip mit einem. Für Maus und Touch funktionieren die x-Buttons trotzdem, bei Touch-Eingabe mit größerer Trefferfläche.

## Wie funktioniert ein Multi Select auf dem Smartphone?

Unter 768px öffnet der Multi Select ein Bottom Sheet statt des Popovers (`responsive` ist standardmäßig an). Das Sheet trägt das Label des Felds als Titel, die Zeilen sind 48px hoch mit der Checkbox am Anfang, und eine fixierte Fußzeile enthält „Clear all“ und „Done (3)“. Die Auswahl im Sheet ist ein Entwurf: Done übernimmt sie, Herunterziehen, ein Tipp daneben oder Escape verwerfen sie. Das passt zu dem, was Leute von einem Sheet erwarten: ein Schritt, den sie wieder abbrechen können.

Das Feld selbst wächst auf Touchscreens in der Standardgröße `md` von 40px auf 44px, und seine Schrift ist auf Smartphones in jeder Größe 16px groß. So zoomt iOS Safari beim Öffnen nicht hinein. Ein Touch-Detail übersieht man leicht: Auf einem Touchscreen bekommt die Suchzeile beim Öffnen der Liste keinen Fokus, denn die Bildschirmtastatur würde die Hälfte der Zeilen verdecken, die die Person antippen wollte. Erst ein Tipp auf die Suchzeile holt die Tastatur hoch. Setz `search="always"`, wenn Tippen der Hauptweg ist, dann bekommt die Suche sofort den Fokus. Unser [Guide zum Bottom Sheet](https://wingo-ui.com/de/blog/react-bottom-sheet-for-the-web) erklärt, warum ein Sheet auf kleinen Bildschirmen besser ist als ein Popover.

## Wie nutze ich den Multi Select mit react-hook-form und zod?

Pack ihn in einen `Controller` und validiere das Array. `ref` und `onBlur` landen auf dem Button des Felds, also kann react-hook-form ihn fokussieren, wenn die Validierung scheitert:

```tsx
"use client";

import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { MultiSelect, type MultiSelectOption } from "@/components/ui/multi-select";
import { Button } from "@/components/ui/button";

const CITIES: MultiSelectOption[] = [
  { value: "new-york", label: "New York" },
  { value: "chicago", label: "Chicago" },
  { value: "seattle", label: "Seattle" },
  { value: "austin", label: "Austin" },
];

const schema = z.object({
  cities: z.array(z.string()).min(1, { error: "Wähl mindestens eine Stadt" }).max(3, { error: "Wähl höchstens 3 Städte" }),
});

type Preferences = z.infer<typeof schema>;

export function CityForm({ save }: { save: (data: Preferences) => Promise<void> }) {
  const form = useForm<Preferences>({ resolver: zodResolver(schema), defaultValues: { cities: [] } });
  return (
    <form onSubmit={form.handleSubmit(save)} className="flex flex-col gap-4">
      <Controller
        name="cities"
        control={form.control}
        render={({ field, fieldState }) => (
          <MultiSelect
            label="Bevorzugte Städte"
            options={CITIES}
            value={field.value}
            onValueChange={field.onChange}
            onBlur={field.onBlur}
            ref={field.ref}
            maxSelected={3}
            error={fieldState.error?.message}
          />
        )}
      />
      <Button type="submit">Speichern</Button>
    </form>
  );
}
```

Ohne Formular-Bibliothek übergibst du `name` und liest die Hidden Inputs in einer Server Action mit `formData.getAll("cities")`. Mit `required` und `name` blockiert ein leeres Feld das Absenden wie ein natives Select, und die Meldung des Browsers zeigt auf das Feld. Validiere trotzdem auf dem Server: Hidden Inputs lassen sich so leicht bearbeiten wie jedes andere Feld.

## Wann nehme ich Multi Select, Tag Input, Combobox oder Checkboxen?

Nimm einen Multi Select für mehrere Werte aus einer bekannten Liste mit etwa sieben oder mehr Optionen. Die anderen wählst du danach, woher die Werte kommen und wie viele es sind:

| Situation | Nimm |
| --- | --- |
| Mehrere Werte aus einer bekannten Liste mit 7 oder mehr Optionen | Multi Select |
| Weniger als etwa 7 Optionen, die alle auf einmal sichtbar sein sollten | Checkbox-Gruppe (`CheckboxGroup` mit `selectAll` und `max`) |
| Werte, die Leute selbst tippen (Keywords, Skills) | Tag Input |
| Eine Liste plus ab und zu ein neuer Wert | Multi Select mit `creatable` und `onCreate` |
| Ein Wert aus einer langen Liste, per Tippen gesucht | Combobox |

Checkboxen direkt auf der Seite sind bei kurzen Listen am leichtesten zu überblicken: kein Klick zum Öffnen, jede Option sichtbar. `CheckboxGroup` aus dem Eintrag [Checkbox](https://wingo-ui.com/components/checkbox) nimmt ebenfalls `options`, `selectAll` und `max`, und der Eintrag ist kostenlos. Der Tag Input ist für offene Werte gedacht; unser [Guide zum React Tag Input](https://wingo-ui.com/de/blog/react-tag-input) behandelt das Aufteilen beim Einfügen und Duplikate. Die Combobox wählt einen einzelnen Wert per Tippen, das passt besser zu einer Stadt oder einem Land.

## Hat shadcn einen Multi Select, und wie schlagen sich andere Bibliotheken?

Einen shadcn Multi Select unter diesem Namen gibt es nicht, aber die shadcn Combobox nimmt `multiple` und rendert Chips mit `ComboboxChips`. Die Details, Stand Oktober 2026, aus der Doku oder dem Quellcode des jeweiligen Projekts:

| | Wingo UI Multi Select | shadcn/ui Combobox | react-select | MUI Autocomplete | react-multi-select-component |
| --- | --- | --- | --- | --- | --- |
| Mehrere Werte | Ja | `multiple` mit `ComboboxChips` | `isMulti` (Standard `false`) | `multiple` | Ja |
| Chip-Überlauf | Gemessenes +N oder feste Anzahl | Nicht dokumentiert | Nicht eingebaut | `limitTags`, eine feste Anzahl, greift ohne Fokus | Kopfzeilentext über `valueRenderer` |
| Alle auswählen | `selectAll`, nur sichtbare Optionen | Nicht dokumentiert | Keine Prop | Nicht eingebaut | `hasSelectAll` (Standard `true`) |
| Maximum | `maxSelected` | Nicht dokumentiert | Keine Prop | Nicht eingebaut | Keine Prop |
| Liste bleibt nach einer Auswahl offen | Ja (`closeOnSelect` aus) | Nicht angegeben | `closeMenuOnSelect` ist standardmäßig `true` | Einschalten mit `disableCloseOnSelect` | Ja (`closeOnChangedValue` aus) |
| Layout auf dem Smartphone | Bottom Sheet unter 768px | Nicht dokumentiert | Dasselbe Menü | Derselbe Popper | Dasselbe Dropdown |
| Styling | Tailwind v4, Quellcode in deinem Repo | Tailwind, Quellcode in deinem Repo | Emotion, Styles-API | MUI-Theme | Eigenes CSS |

Quellen: [shadcn/ui Combobox](https://ui.shadcn.com/docs/components/combobox), [Quellcode von react-select](https://github.com/JedWatson/react-select/blob/master/packages/react-select/src/Select.tsx), [MUI Autocomplete](https://mui.com/material-ui/react-autocomplete/), [react-multi-select-component](https://github.com/hc-oss/react-multi-select-component).

Ein Standardwert von react-select überrascht viele: `hideSelectedOptions` übernimmt den Wert von `isMulti`. Gewählte Optionen verschwinden also aus dem Menü, und eine Checkbox-Liste gibt es erst, wenn du die Prop auf `false` setzt. Das passt zu einem Tag-Feld, in dem man zuerst tippt, und verwirrt Leute, die eine Liste überfliegen.

Läuft deine App schon auf MUI, ist der Autocomplete mit `multiple`, `limitTags` und Checkbox-Zeilen die vernünftige Wahl. react-select passt zu einem Multi Select, bei dem Tippen im Vordergrund steht, wenn du mit Emotion-Styling leben kannst, und react-multi-select-component ist ein MIT-Paket, bei dem Alle auswählen standardmäßig an ist. Die shadcn Combobox ist ein guter Start, wenn du nur Chips brauchst. Willst du gemessenen Überlauf, Alle auswählen über Suchergebnisse, ein Maximum und ein Sheet fürs Smartphone schon fertig gebaut, dann deckt unser Multi Select genau das ab.

## Womit fängst du am besten an?

Öffne die [Doku zum Multi Select](https://wingo-ui.com/components/multi-select). Dort findest du den Playground und Beispiele für Avatare, ein Maximum, Anzeige als Anzahl oder Text, umbrechende Chips, Gruppen mit Alle auswählen und asynchrone Optionen. Multi Select, Tag Input und Combobox gehören zu Wingo UI Pro, $8 im Monat, $80 im Jahr oder $150 einmalig, Stand Oktober 2026 ([Preise](https://wingo-ui.com/de/pricing)). Für den Rest des Formulars lies den [Guide zu React-Formularkomponenten](https://wingo-ui.com/de/blog/react-form-components-guide), und mehr findest du in der [Kategorie Komponenten-Guides](https://wingo-ui.com/de/blog/category/components).

## Komponenten in diesem Artikel

- [Multi Select](https://wingo-ui.com/components/multi-select) (Pro): A React multi select: pick several options shown as chips with a +N overflow, with search, select all, a maximum and async options. Installation: `npx wingo-ui@latest add multi-select`
- [Combobox](https://wingo-ui.com/components/combobox) (Pro): A React combobox and autocomplete: type to filter a list or search a server, with matches in bold, a create row and a phone search sheet. Installation: `npx wingo-ui@latest add combobox`
- [Tag Input](https://wingo-ui.com/components/tag-input) (Pro): A React tag input that turns typed, pasted or suggested text into removable chips, for tags, keywords and email recipients. Installation: `npx wingo-ui@latest add tag-input`
- [Checkbox](https://wingo-ui.com/components/checkbox) (Free): A React checkbox that is on, off or mixed, with a check that draws itself, full-row labels, cards, and groups with select all. Installation: `npx wingo-ui@latest add checkbox`

## Häufige Fragen

### Wie baue ich ein Multi-Select-Dropdown in React?

Halte ein Array der gewählten Werte im State, render einen Button, der eine Listbox mit aria-multiselectable öffnet, und schalte bei jedem Klick auf eine Zeile den Wert um, ohne die Liste zu schließen. Eine React Multi Select Komponente wie der Multi Select von Wingo UI ergänzt Chips mit +N-Überlauf, Suche, Alle auswählen, ein Maximum, asynchrone Optionen und ein Bottom Sheet fürs Smartphone.

### Hat shadcn/ui einen Multi Select?

Eine Komponente namens Multi Select gibt es in shadcn/ui Stand Oktober 2026 nicht, aber die Combobox nimmt eine Prop multiple und zeigt die Auswahl als Chips. Die Doku zeigt keine Zeile für Alle auswählen, keinen +N-Überlauf und keinen Drawer fürs Smartphone. Dafür brauchst du eigenen Code oder eine Komponente aus der Community.

### Wie zeige ich +N, wenn die Chips eines Multi Select überlaufen?

Miss die Chips in einer versteckten Ebene, nimm sie einzeln dazu, bis der nächste Chip plus der +N-Chip breiter als das Feld wären, und render nur diese, dann einen Chip mit der restlichen Anzahl. Miss mit einem ResizeObserver neu, denn die Breite des Felds und das Laden von Schriften ändern das Ergebnis.

### Soll Alle auswählen jede Option wählen oder nur die gefilterten?

Nur die Optionen, die die Person sieht. Wer nach „neu“ sucht und Alle auswählen drückt, erwartet die passenden Zeilen, nicht die 200 versteckten. Der Multi Select von Wingo UI schaltet die sichtbaren Optionen um und stoppt bei maxSelected.

### Wie sende ich einen Multi Select mit einem Formular ab?

Render pro Wert ein Hidden Input mit demselben name und lies sie mit formData.getAll(name), oder pack die Komponente in den Controller von react-hook-form und validiere das Array mit zod. Der Multi Select von Wingo UI rendert die Hidden Inputs, wenn du name übergibst.

---

Quelle: https://wingo-ui.com/de/blog/react-multi-select
