React + MotionFilter

Mehrfachauswahl-Dropdown

Ein Dropdown mit Suchfeld oben, in dem du mehrere Optionen abhakst und siehst, wie viele gewählt sind.

Einstellungen

6

Code

import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Check, ChevronDown, Search } from "lucide-react";

const sizeMap = {
  sm: { pad: "px-3 py-1.5", text: "text-xs" },
  md: { pad: "px-4 py-2", text: "text-sm" },
  lg: { pad: "px-5 py-2.5", text: "text-base" },
} as const;

interface MultiSelectProps {
  accent?: string;
  size?: keyof typeof sizeMap;
  options?: string[];
  value?: string[];
  onChange?: (value: string[]) => void;
}

export default function MultiSelect({
  accent = "#0071e3",
  size = "md",
  options = ["Design", "Code", "Audio", "Video", "3D", "Daten"],
  value,
  onChange,
}: MultiSelectProps) {
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState("");
  const [internal, setInternal] = useState<string[]>([]);
  const selected = value ?? internal;
  const s = sizeMap[size];
  const filtered = options.filter((o) => o.toLowerCase().includes(query.toLowerCase()));

  const toggle = (o: string) => {
    const next = selected.includes(o) ? selected.filter((x) => x !== o) : [...selected, o];
    if (value === undefined) setInternal(next);
    onChange?.(next);
  };

  return (
    <div className="relative w-64">
      <button
        type="button"
        aria-expanded={open}
        onClick={() => setOpen((o) => !o)}
        className={`flex w-full items-center justify-between rounded-full border border-black/10 bg-white font-medium dark:border-white/10 dark:bg-[#1c1c1e] ${s.pad} ${s.text}`}
      >
        <span className={selected.length ? "" : "text-[#86868b]"}>
          {selected.length ? `${selected.length} seleccionados` : "Auswählen..."}
        </span>
        <ChevronDown className={`h-4 w-4 transition-transform ${open ? "rotate-180" : ""}`} />
      </button>
      <AnimatePresence>
        {open && (
          <motion.div
            initial={{ opacity: 0, y: -6 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -6 }}
            className="absolute z-20 mt-2 w-full overflow-hidden rounded-2xl border border-black/10 bg-white shadow-[0_1px_2px_rgba(0,0,0,0.03),0_8px_28px_rgba(0,0,0,0.05)] dark:border-white/10 dark:bg-[#1d1d1f]"
          >
            <div className="flex items-center gap-2 border-b border-black/10 px-3 py-2 dark:border-white/10">
              <Search className="h-3.5 w-3.5 text-[#86868b]" />
              <input
                autoFocus
                value={query}
                onChange={(e) => setQuery(e.target.value)}
                placeholder="Suchen ..."
                className="w-full bg-transparent text-sm outline-none placeholder:text-[#86868b]"
              />
            </div>
            <div className="max-h-44 overflow-y-auto p-1 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-black/20 dark:[&::-webkit-scrollbar-thumb]:bg-white/20">
              {filtered.map((o) => {
                const on = selected.includes(o);
                return (
                  <button
                    key={o}
                    type="button"
                    aria-pressed={on}
                    onClick={() => toggle(o)}
                    className="flex w-full items-center justify-between rounded-md px-2.5 py-1.5 text-sm hover:bg-black/5 dark:hover:bg-white/10"
                  >
                    {o}
                    {on && <Check className="h-4 w-4" style={{ color: accent }} />}
                  </button>
                );
              })}
              {!filtered.length && (
                <p className="px-2.5 py-2 text-sm text-[#86868b]">Keine Ergebnisse</p>
              )}
            </div>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
}

Prompt für KI

Füg ihn in ChatGPT, Claude oder Cursor ein und der Baustein wird mit diesen Einstellungen an dein Projekt angepasst, auch ohne React.

Mehr aus Filter

Wofür du ihn nutzen kannst

Wenn es für Chips zu viele Optionen gibt, hält ein Dropdown die Seite aufgeräumt. Etwa bei Sprachen im Profil eines Übersetzers, Tags für einen Blogartikel oder Leuten, denen man eine Aufgabe zuweist. Das Suchfeld filtert beim Tippen, deshalb bleibt dieses Multi-Select-Dropdown auch bei langen Listen flott.

Der Button zeigt eine Zahl statt aller Namen und bleibt so kompakt. Müssen Leute ihre Auswahl auf einen Blick sehen, zeig sie als Chips darunter. Und ersetz den Platzhalter durch etwas Konkretes wie „Sprachen wählen“.