React Filter Builder: AND/OR-Chips, gespeicherte Ansichten
Ein React Filter Builder ist die Leiste über einer Liste, in der Nutzer Bedingungen nacheinander hinzufügen: Stadt ist Chicago, Saldo mindestens $5,000, letzte Rechnung in den letzten 30 Tagen. Jede Bedingung wird zu einem Chip, die Chips werden mit AND oder OR verknüpft, und die Zahl daneben sagt, wie viele Zeilen übrig sind. Das Muster kennst du aus Issue-Trackern und tabellenartigen Datenbanken, und genau danach fragen Backoffice-Nutzer, sobald ein Suchfeld und ein Status-Dropdown nicht mehr reichen. Dieser Guide zeigt, wie du einen mit dem Filter Builder von Wingo UI baust, wie du ihn an eine Data Table und an einen Server anbindest und wann ein schlichtes Filter Panel die bessere Wahl ist.
Was ist ein Filter Builder, und wann brauchst du einen?
Ein Filter Builder bearbeitet einen kleinen Baum: eine Wurzelgruppe mit einem Kombinator (and oder or) und einer Liste von Bedingungen. Jede Bedingung besteht aus Feld, Operator und Wert. Eine verschachtelte Gruppe bringt Klammern ins Spiel, „in Chicago und (überfällig oder Saldo über $5,000)“ ist also ein AND an der Wurzel mit einer Bedingung und einer OR-Gruppe darin.
Du brauchst einen, wenn sich die Fragen an eine Liste von Tag zu Tag ändern und Zahlen, Datumswerte oder ein OR enthalten. Sales Ops filtert Kunden, der Support Tickets, die Buchhaltung Rechnungen: Niemand will für jede Frage einen neuen, fest verdrahteten Tab. Braucht deine Liste nur „Status ist X“ und eine Suche, kann die Data Table das schon mit ihren Spaltenfiltern und Quick-Filter-Chips, und ein Builder wäre unnötiger Ballast. Welche Filter-UI für welchen Zweck passt:
Hat shadcn einen Filter Builder?
Nein. Der Data-Table-Guide von shadcn (opens in a new tab) (geprüft am 6. November 2026) fügt ein Textfeld hinzu, das die E-Mail-Spalte über TanStack Table filtert, und sagt, dass du andere Spalten auf dieselbe Weise filtern kannst. Facettenfilter, AND / OR-Bedingungen und gespeicherte Ansichten gehören nicht dazu. „shadcn filters“ oder ein „shadcn data table filter“ mit Bedingungen heißt in der Praxis also: ein Registry-Item auswählen oder selbst schreiben.
Wenn du nach „react query builder shadcn“ suchst, landest du bei React Query Builder (opens in a new tab). Stand 6. November 2026 liefert das Projekt Kompatibilitätspakete für Ant Design, Chakra UI, Mantine, MUI und weitere, dazu ein shadcn-Registry-Item, das du mit npx shadcn add https://react-querybuilder.js.org/r/query-builder.json installierst. Laut QueryBuilder-Doku (opens in a new tab) steht maxLevels standardmäßig auf unbegrenzter Verschachtelung, und zwischen den Regeln sind unabhängige Kombinatoren möglich. Die Export-Doku (opens in a new tab) nennt Ziele für formatQuery wie SQL, Prisma, Drizzle, MongoDB, CEL und JsonLogic. Das ist das bessere Werkzeug, wenn du einen kompletten Regelbaum brauchst, den du speicherst und als Abfrage ausführst, etwa in einem Reporting-Tool. Was direkt an der Liste passiert, deckt es nicht ab: Chips, Trefferzahl, gespeicherte Ansichten oder ein Smartphone-Layout. Um genau diese Seite geht es im Rest dieses Guides.
Wie fügst du einer React-Liste einen Filter Builder hinzu?
Installier ihn mit der CLI. Sie bringt lib/filters (Filtermodell, Auswertung und Serialisierung) und die Editoren mit, die er nutzt:
Beschreib deine Felder einmal. Jedes Feld hat eine id (den Schlüssel in der Zeile oder einen accessor), ein label und einen type: text, number, currency, date, select, multi-select oder boolean. Der Typ bestimmt, welche Operatoren das Feld anbietet und welcher Editor aufgeht: ein Textfeld, ein Zahlen- oder Betragsfeld, ein Kalender mit Schnellauswahl oder eine durchsuchbare Checkliste.
applyFilter wendet denselben Baum auf deine Zeilen an: Textvergleiche ignorieren Groß- und Kleinschreibung sowie Akzente, Datumswerte werden nach lokalem Tag verglichen, und unvollständige Bedingungen werden übersprungen. Ein halb ausgefüllter Chip leert die Liste also nie. Mit previewCount zeigt das Sheet auf dem Smartphone schon „Show 3 results“, bevor irgendetwas angewendet ist.
$ npx wingo-ui@latest add filter-builderWelche Props sind am wichtigsten?
Sechs Props bestimmen, wie sich der Builder verhält; der Rest betrifft das Aussehen (size, radius, tone) und den kontrollierten State.
variant:chips(der Standard) setzt jede Bedingung inline mit einem eigenen Editor-Popover.panelzeigt einen einzigen Filters-Button mit Zähler, der einen Editor mit Zeilen öffnet (eine Zeile pro Bedingung). Dort lassen sich verschachtelte Gruppen am leichtesten bearbeiten.applyMode:instantruftonValueChangebei jeder vollständigen Änderung auf (Text nach 300ms Pause).applyhält einen Entwurf mit Cancel und Apply. Das willst du, wenn jede Änderung einen Request kostet.allowOrundallowGroups: Beide stehen standardmäßig auftrue. Schalte OR für Zielgruppen ab, die es nie brauchen; der Builder bleibt dann eine schlichte AND-Liste. Gruppen gehen eine Ebene tief. Das reicht für „A und (B oder C)“, ohne dass aus der UI ein Baumeditor wird.resultCountundloading: Die Zahl steht in einer Live-Region mitaria-livepolite, Screenreader hören nach jeder Änderung also „24 results“.loadingzeigt einen Spinner, während der Server antwortet.maxChips: Auf dem Desktop wandern Chips über dieser Zahl in einen „+3“-Chip, der das Panel öffnet.labels: alle Texte, standardmäßig Englisch. Die Prop nimmt ein Teilobjekt, du gibst also nur die Schlüssel mit, die du übersetzt;DEFAULT_FILTER_BUILDER_LABELSenthält den kompletten englischen Satz.
Felder können ihre Operatoren außerdem mit operators: [...] einschränken. Mach das, wenn der Server nur einen Teil davon unterstützt, damit niemand einen Filter baut, den dein Backend stillschweigend ignoriert.
$ npx wingo-ui@latest add filter-builderWie funktionieren gespeicherte Ansichten?
Eine Ansicht ist ein benannter Filter: { id, name, filter, readOnly?, pinned? }. Gib die Ansichten in views mit, und das Ansichtsmenü listet sie auf, angepinnte zuerst, ganz oben „All records“. Wer eine auswählt, ersetzt damit den Filter. Wird sie danach bearbeitet, steht „(modified)“ dahinter, und das Menü bietet Save changes oder Save as new view an.
Das Speichern implementierst du selbst, denn nur deine App weiß, wo Ansichten (Saved Views) liegen. Gib die gespeicherte Ansicht aus onSaveView zurück, dann wird sie aktiv. Wird das Promise abgelehnt, lässt der Builder das Namensfeld offen und meldet den Fehler, damit nichts verloren geht, was der Nutzer getippt hat. Ein abgelehntes onDeleteView meldet seinen eigenen Fehler und lässt die Ansicht im Menü.
serializeFilter (aus @/lib/filters) macht aus dem Baum einen kurzen, URL-sicheren String, und parseFilter liest ihn wieder ein. Derselbe String funktioniert für Links mit ?filter=. So fügt jemand aus deinem Team die URL ein und sieht genau die Liste, die du gesehen hast.
Wie verbindest du ihn mit einer Data Table?
Halte den Filter-State oberhalb beider Komponenten und gib die gefilterten Zeilen nach unten weiter. Die Tabelle behält ihre eigene Suche, Sortierung, Auswahl und die Karten auf dem Smartphone; der Builder entscheidet, welche Zeilen sie bekommt.
Lass die filter-Optionen pro Spalte in der Tabelle weg, wenn der Builder da ist. So gibt es einen Ort zum Filtern statt zwei, die sich widersprechen. Für Listen mit Tausenden Zeilen, die im Browser bleiben, ist das alles. Für die TanStack-Seite der Tabelle selbst lies den Walkthrough zur Data Table mit TanStack Table v9.
Wie führst du den Filter auf dem Server aus?
In lib/filters steckt kein React, also funktioniert dasselbe parseFilter auch in einem Route Handler. Für eine Datenbank läufst du durch den Baum und machst aus jeder Bedingung eine parametrisierte Klausel. Der wichtige Teil ist die Whitelist: Feld-IDs werden auf bekannte Spalten abgebildet, und alles Unbekannte fliegt raus, statt in den SQL-String zu wandern.
Auf dem Client schickst du serializeFilter(filter) aus onValueChange, setzt loading, solange der Request läuft, und gibst die Gesamtzahl aus der Antwort als resultCount weiter. Kombinier das mit applyMode="apply", wenn die Abfrage langsam ist. Beschränk die operators jedes Felds auf die Fälle, die dein switch behandelt, damit die UI nie einen anbietet, den der Server verwirft. Achte außerdem darauf, dass Client und Server Text gleich vergleichen: applyFilter ignoriert Groß- und Kleinschreibung sowie Akzente, = in Postgres tut beides nicht. Nimm also ILIKE, eine citext-Spalte oder unaccent(), wenn die Zahlen in Browser und Server übereinstimmen müssen.
Wie verhält er sich auf dem Smartphone?
Unter 768px bleiben die Chips sichtbar und scrollen seitlich, jeder mit einem ×, das ihn sofort entfernt. Alles andere zieht in ein einziges Bottom Sheet hinter einem Filters-Button mit Zähler: Bedingungen als Zeilen, and / or als Segmented Control, und „+ Add filter“ führt mit einer kurzen Slide-Animation erst in die Feldliste und dann in den Editor. Nichts ändert sich, bis der Nutzer auf „Show 24 results“ tippt; wer das Sheet schließt, verwirft den Entwurf. Das ist Absicht: Ein Popover an einem 32px hohen Chip würde genau die Liste verdecken, die gefiltert werden soll, und ein Sofortfilter hinter einem Sheet in voller Höhe ändert eine Liste, die niemand sieht. Bei Touch-Bedienung wachsen die Touch-Ziele auf 44px. Setz responsive={false} nur, wenn deine App nur auf dem Desktop läuft.
Wann ist ein einfaches Filter Panel die bessere UI?
Wenn die Leute, die filtern, Kunden sind, die durch einen Katalog stöbern. Wer ein gebrauchtes Smartphone unter $300 sucht, will Apple anhaken, einen Preisbereich ziehen und „Show 1,240 results“ sehen; in Operatoren oder OR-Gruppen denkt dabei niemand. Dafür ist das Filter Panel gebaut: eine Seitenleiste mit Facetten, die auf dem Desktop jede Änderung direkt anwendet (Checkbox-Listen mit Zählern, Chips, Preis über einem Histogramm, Ort mit Radius, Schalter), und auf dem Smartphone ein Sheet in voller Höhe mit Live-Zähler.
$ npx wingo-ui@latest add filter-panelEine gute Faustregel: Wenn du jeden sinnvollen Filter vorab aufzählen kannst und jeder nur eine Handvoll Werte hat, nimm das Panel. Wenn Nutzer sich eigene Fragen ausdenken und speichern sollen, nimm den Builder. Manche Apps nutzen beides: das Panel im öffentlichen Katalog, den Builder im Admin-Bereich dahinter.
Was sind die typischen Fallstricke?
- Chips, die nichts filtern. Ein Chip mit Operator, aber ohne Wert sieht aktiv aus und ist es nicht. Der Builder entfernt einen unvollständigen Chip, sobald sein Editor zugeht; wenn du selbst einen baust, mach es genauso.
- Zwei Filtersysteme auf einem Screen. Spaltenfilter im Tabellenkopf plus ein Builder darüber geben zwei Antworten auf die Frage „Warum ist diese Zeile ausgeblendet?“. Entscheide dich für eins.
- OR ohne Klammern. „Chicago und überfällig oder VIP“ ist mehrdeutig. Halte OR an der Wurzel oder in einer sichtbaren Gruppe, und zeig den Filter als Satz (
describeFilterinlib/filtersschreibt ihn) dort, wo man ihn lesen kann. - Stille Lücken im Server. Ignoriert das Backend einen Operator, sehen Nutzer falsche Zahlen. Schränk
operatorspro Feld ein. - Keine Trefferzahl. Ohne „24 results“ weiß niemand, ob ein Filter überhaupt etwas bewirkt hat. Gib
resultCountmit, und0ist eine echte Antwort.
Mehr Guides wie diesen findest du in der Kategorie Komponenten-Guides, oder du startest mit dem Pillar-Artikel zu React-Formularkomponenten. Er nutzt dieselbe Feld-Anatomie wie die Editoren hier.
Wo du anfängst
Filter Builder, Data Table und Filter Panel sind Pro-Komponenten und in jedem Plan auf der Preisseite enthalten. Am schnellsten beurteilst du den Builder im Playground auf seiner Doku-Seite: Stell variant, applyMode und allowGroups um und schau, was sich ändert. Wenn er passt, installier ihn mit npx wingo-ui@latest add filter-builder und pass fields an deine eigene Liste an.
Häufige Fragen
Was ist der Unterschied zwischen einem Filter Builder und einem Query Builder?
Ein Query Builder bearbeitet meist einen kompletten Regelbaum mit beliebiger Verschachtelung und exportiert ihn nach SQL, MongoDB oder in eine andere Abfragesprache. Er passt also zu Admin-Tools, die Abfragen speichern. Ein Filter Builder ist die leichtere Variante direkt an der Liste: ein Chip pro Bedingung, AND / OR, eine Ebene Gruppen, gespeicherte Ansichten und eine Trefferzahl neben der Liste.
Hat shadcn/ui eine Filter-Builder-Komponente?
Nein. Stand November 2026 zeigt der Data-Table-Guide von shadcn ein Textfeld, das über TanStack Table eine Spalte filtert, und verweist für alles Weitere auf die TanStack-Doku. AND / OR-Bedingungen, verschachtelte Gruppen und gespeicherte Ansichten bleiben dir oder einem Registry-Item von Dritten überlassen.
Wie speichere ich einen Filter in der URL?
Serialisier den Filterbaum zu einem kurzen, URL-sicheren String und leg ihn in einen Query-Parameter wie ?filter=. In Wingo UI schreibt serializeFilter(filter) aus lib/filters den String, und parseFilter(text) liest ihn wieder ein. Bei einem veralteten oder von Hand bearbeiteten Link gibt es null zurück, und die Seite zeigt einfach die ungefilterte Liste.
Sollen Filter sofort greifen oder erst mit einem Apply-Button?
Lass sie sofort greifen, wenn Filtern billig ist, etwa bei einer Liste, die schon im Browser liegt. Dann sieht man die Wirkung jeder Änderung direkt. Nimm einen Apply-Button, wenn jede Änderung einen Server-Request oder eine langsame Abfrage kostet, und arbeite auf Smartphones immer mit einem Entwurf, weil der Filter-Editor dort die Liste verdeckt.
Wie filtere ich eine React-Tabelle mit AND / OR-Bedingungen auf dem Server?
Schick den Filterbaum an einen Endpoint, lauf rekursiv durch ihn und mach aus jeder Bedingung eine parametrisierte SQL-Klausel, verbunden mit AND oder OR, Gruppen in Klammern. Bilde Feld-IDs über eine Whitelist auf Spalten ab, damit ein Client nie einen Spaltennamen einschleusen oder einen Operator schicken kann, den du nicht unterstützt.
- Filter Builder
- Query Builder
- Datentabelle
- Gespeicherte Ansichten
- React
