MultiSelectDropdown
ui filteroverlayform
Mehrfach-Checkbox-Filter mit Einschluss-Logik: leere Auswahl = „alles sichtbar", sonst nur die gewählten Keys. „Alle anzeigen" oben setzt zurück. MultiSelectList ist die wiederverwendbare Checkbox-Liste ohne Trigger.
alle sichtbar
Abhängigkeiten
Barrierefreiheit
- Native Checkboxen in <label> — Tastatur/Screenreader nativ; accent-color = --accent.
Beispiele
Buch-Filter
const [sel, setSel] = useState<Set<string>>(new Set());
<MultiSelectDropdown
ariaLabel="Buch" placeholder="Buch"
options={[{ key: 'gen', label: 'Genesis' }, { key: 'ex', label: 'Exodus' }]}
selected={sel} onChange={setSel}
/> Genutzte Tokens · 6
--accent--bg--border--muted--radius-sm--space-2
Quelldateien
components/ui/MultiSelectDropdown.tsx tsx
import { type ReactNode } from 'react';
import { FilterDropdown } from './FilterDropdown';
import styles from './MultiSelectDropdown.module.css';
export interface MultiSelectOption {
key: string;
label: ReactNode;
}
interface Props {
ariaLabel: string;
/** Triggertext ohne Auswahl, z. B. „Buch". Bei Auswahl wird „ · N" angehängt. */
placeholder: string;
options: MultiSelectOption[];
/** Einschluss-Logik: leeres Set = alles sichtbar, sonst nur die gewählten Keys. */
selected: Set<string>;
onChange: (next: Set<string>) => void;
align?: 'left' | 'right';
/** Text der „Alle anzeigen"-Reset-Zeile (i18n). */
showAllLabel?: string | undefined;
}
/** Dropdown mit Mehrfach-Checkbox-Auswahl. „Alle anzeigen" oben setzt die
* Auswahl zurück (leer = alles sichtbar). */
export function MultiSelectDropdown({
ariaLabel,
placeholder,
options,
selected,
onChange,
align = 'left',
showAllLabel,
}: Props) {
return (
<FilterDropdown ariaLabel={ariaLabel} label={placeholder} count={selected.size} align={align}>
<MultiSelectList
options={options}
selected={selected}
onChange={onChange}
showAllLabel={showAllLabel}
/>
</FilterDropdown>
);
}
interface MultiSelectListProps {
options: MultiSelectOption[];
/** Einschluss-Logik: leeres Set = alles sichtbar, sonst nur die gewählten Keys. */
selected: Set<string>;
onChange: (next: Set<string>) => void;
showAllLabel?: string | undefined;
}
/** Reine Checkbox-Liste (ohne Trigger/Popover) für Mehrfachauswahl mit
* „Alle anzeigen"-Reset. Geteilt vom `MultiSelectDropdown` und von
* kombinierten Filter-Popovern. */
export function MultiSelectList({
options,
selected,
onChange,
showAllLabel = 'Alle anzeigen',
}: MultiSelectListProps) {
const toggle = (key: string) => {
const next = new Set(selected);
if (next.has(key)) next.delete(key);
else next.add(key);
onChange(next);
};
return (
<div className={styles.list}>
<label className={styles.option}>
<input
type="checkbox"
checked={selected.size === 0}
onChange={() => onChange(new Set())}
/>
<span className={styles.muted}>{showAllLabel}</span>
</label>
<div className={styles.separator} role="separator" />
{options.map((opt) => (
<label key={opt.key} className={styles.option}>
<input
type="checkbox"
checked={selected.has(opt.key)}
onChange={() => toggle(opt.key)}
/>
{opt.label}
</label>
))}
</div>
);
}
components/ui/MultiSelectDropdown.module.css css
.list {
display: flex;
flex-direction: column;
gap: 1px;
min-width: 180px;
max-height: 320px;
overflow-y: auto;
}
.option {
display: flex;
align-items: center;
gap: var(--space-2);
padding: 6px 8px;
border-radius: var(--radius-sm);
cursor: pointer;
user-select: none;
}
.option:hover {
background: var(--bg);
}
.option input {
margin: 0;
accent-color: var(--accent);
flex-shrink: 0;
}
.muted {
color: var(--muted);
}
.separator {
height: 1px;
margin: 4px 2px;
background: var(--border);
}
Holen
# MCP (Claude Code / Cursor)
get_component({ name: "multi-select-dropdown" })
# Registry direkt
https://www.ekklesi.tools/design/registry/components/multi-select-dropdown.json