DEIN TÄGLICHES UI/UX-COACHING / LEKTION 07

Suche und Filter: Zwischen Kontrolle und Komplexität

Mehr Filter bedeuten nicht automatisch bessere UX. Heute gestaltest du eine Suche, bei der Menschen ihre Auswahl verstehen, Ergebnisse einschätzen und Einschränkungen gezielt wieder aufheben können.

ca. 15 Minuten inklusive Übung

Dein Lernziel

Du kombinierst Quick-Filter und Dialog zu einem nachvollziehbaren Ablauf. Dabei unterscheidest du Auswahl und Anwendung, erklärst die Filterlogik und gestaltest einen hilfreichen Zustand ohne Treffer.

01 / KONTROLLE UND KOMPLEXITÄT · 3 MINUTEN

Vier Fragen, die dein Filtersystem beantworten muss.

Eine Buch-App bietet 2.500 Titel. Menschen können nach Genre, Trope, Stimmung und Bewertung suchen. Eine Trope ist ein wiederkehrendes Erzählmotiv, etwa „Enemies to Lovers“. Eine Desktop-Sidebar lässt sich auf einem Smartphone nicht einfach unverändert weiterverwenden.

  • Was kann ich filtern? Die verfügbaren Kategorien müssen auffindbar sein.
  • Was ist aktiv? Die Auswahl bleibt auch bei geschlossenem Dialog erkennbar.
  • Wie wirkt die Auswahl? Trefferzahl und Verknüpfung erklären das Ergebnis.
  • Wie komme ich zurück? Einzelnes Entfernen und Zurücksetzen bleiben erreichbar.

Drei mögliche Patterns

  • Alle Filter sichtbar: Gute Entdeckbarkeit, aber hohe Informationsdichte. Auf kleinen Displays können die Ergebnisse weit nach unten rutschen.
  • Alle Filter im Dialog: Mehr Platz für die Bücher, aber zusätzliche Schritte und weniger sichtbare Auswahlmöglichkeiten.
  • Quick-Filter plus Dialog: Direkter Zugriff auf ausgewählte Optionen, dafür müssen beide Wege denselben Zustand verwenden. Welche Optionen häufig gebraucht werden, ist zunächst eine zu prüfende Annahme.

Wie bei Progressive Disclosure in Lektion 4 geht es nicht darum, möglichst viel auszublenden. Entscheidend ist, welche Information für die aktuelle Aufgabe sichtbar bleiben muss.

02 / DEINE CHALLENGE · 8 MINUTEN

Von 2.500 Büchern zu einer nachvollziehbaren Auswahl.

Gestalte eine mobile Buchsuche bei 375 px Breite. Die Nutzerin sucht Romantasy mit Enemies to Lovers, der Stimmung Spannend und einer Bewertung von mindestens vier Sternen. In diesem fiktiven Szenario bleiben damit 18 Bücher übrig.

  1. Zustand A – Start: Noch keine Filter aktiv. Entscheide, welche Optionen sofort sichtbar sind und wie weitere Filter geöffnet werden.
  2. Zustand B – Auswahl: Vier Filter sind angewendet. Zeige die 18 Treffer, alle aktiven Einschränkungen und eine Möglichkeit, nur eine davon zu entfernen.
  3. Zusätzlicher Zustand – keine Treffer: Erhalte die Auswahl und biete einen konkreten Weg, die Suche wieder zu erweitern.

Die Bücher bleiben im Mittelpunkt. Achte auf gut bedienbare Chips, den Ergebniszähler, einen wiederauffindbaren Filter-Button und sichtbare Fokuszustände. Aktive Optionen müssen ohne Farbe verständlich sein.

Notiere außerdem: Wann wird eine Auswahl angewendet? Was passiert bei Abbrechen und Escape? Wie wirken mehrere Werte derselben Kategorie zusammen? Begründe zwei Entscheidungen und einen Nachteil deines Entwurfs.

03 / EINE MÖGLICHE MUSTERLÖSUNG

Auswählen, verstehen, gezielt ändern.

Die folgende Suche funktioniert vollständig lokal. Öffne zuerst „Alle Filter“, stelle die vier gewünschten Kriterien ein und wende sie gemeinsam an. Mit den Übungsbuttons kannst du die Zielsuche oder eine Kombination ohne Treffer direkt laden.

Interaktive Buchsuche öffnen

Lernbeispiel mit 2.500 synthetischen Datensätzen. Titel und Bewertungen sind erfunden. Die Beispielsuche ergibt konstruktionsbedingt genau 18 Treffer; das ist kein Nutzungs- oder Testergebnis.

DEINE NÄCHSTE GESCHICHTE

Bücher entdecken

Schnellauswahl

Mehrere Werte innerhalb einer Kategorie: ODER. Zwischen Kategorien und Titelsuche: UND.

2.500 Bücher gefunden · keine Filter aktiv

  • Beispielbuch 0001

    Romantasy · Enemies to Lovers

    Spannend · 4,5 von 5 Sternen

  • Beispielbuch 0002

    Romantasy · Enemies to Lovers

    Spannend · 4,5 von 5 Sternen

  • Beispielbuch 0003

    Romantasy · Enemies to Lovers

    Spannend · 4,5 von 5 Sternen

  • Beispielbuch 0004

    Romantasy · Enemies to Lovers

    Spannend · 4,5 von 5 Sternen

  • Beispielbuch 0005

    Romantasy · Enemies to Lovers

    Spannend · 4,5 von 5 Sternen

  • Beispielbuch 0006

    Romantasy · Enemies to Lovers

    Spannend · 4,5 von 5 Sternen

Gezeigt: 6 von 2.500 Treffern. Die Vorschau zeigt höchstens sechs Bücher und hat keine weitere Ergebnisseite.

Bücher filtern

Änderungen gelten erst nach „Bücher anzeigen“. Abbrechen oder Escape verwirft sie. Die Titelsuche bleibt erhalten.

ODER innerhalb einer Kategorie, UND zwischen den Kategorien.

Genre
Trope
Stimmung
Bewertung

Vorschau: 2.500 Treffer mit dieser Auswahl.

Warum diese Entscheidungen?

  • Zwei direkte Einstiege: Romantasy und Enemies to Lovers sind für die vorgegebene Aufgabe als Quick-Filter sichtbar. In einem realen Produkt müsste ihre Priorisierung mit tatsächlichen Suchaufgaben und Nutzungsmustern begründet werden.
  • Ein gemeinsamer Zustand: Ein Quick-Filter und dieselbe Option im Dialog repräsentieren dieselbe Auswahl. Ein aktivierter Quick-Filter zeigt zusätzlich ein Häkchen; aria-pressed vermittelt den Zustand programmatisch.
  • Änderungen sammeln: Im Dialog bearbeitest du eine Kopie der angewendeten Auswahl. Die Trefferzahl ist eine Vorschau. Erst „Bücher anzeigen“ übernimmt die Änderungen. Abbrechen und Escape erhalten die bisherige Ergebnisliste.
  • Einzelnes Entfernen: Aktive Chips nennen Kategorie und Wert. Der zugängliche Name ergänzt „entfernen“. Nach dem Entfernen geht der Fokus zum weiterhin vorhandenen Filter-Button, damit er nicht mit dem Chip verloren geht.
  • Ein hilfreicher Empty State: Die Auswahl bleibt sichtbar. Du kannst einzelne Einschränkungen entfernen oder Suche und Filter gemeinsam zurücksetzen. Das System lockert keine Kriterien unbemerkt.
  • Bewusst begrenzte Vorschau: Die Trefferzahl wird aus 2.500 synthetischen Datensätzen berechnet. Sichtbar sind höchstens sechs Beispielbücher; eine vollständige Ergebnisnavigation gehört nicht zu dieser Übung. Die Titelsuche wird erst mit „Suchen“ übernommen.

Der Nachteil: Sofortige Quick-Filter und gesammelte Dialogänderungen verlangen zwei verständliche Interaktionsregeln. Beschriftung, sichtbare Auswahl und konsistenter Zustand sollen diesen Unterschied erklären. Die Musterlösung ist ein eigener, ungetesteter Gestaltungsvorschlag.

React-Code, CSS und Filterlogik ansehen

Hier steht der tatsächliche Code der Musterlösung einschließlich der synthetischen Beispieldaten. Zum Übernehmen werden React und angepasste Importpfade benötigt.

coaching-book-search.tsx

"use client";

import { useId, useRef, useState } from "react";
import { bookFilterKeys, bookFilterOptions, copyBookFilters, emptyBookFilters, filterCoachingBooks, targetBookFilters, toggleBookFilter, type BookFilters } from "@/lib/coaching-book-filters";
import "./coaching-book-search.css";

export function CoachingBookSearch() {
  const [applied, setApplied] = useState<BookFilters>(emptyBookFilters);
  const [draft, setDraft] = useState<BookFilters>(emptyBookFilters);
  const [queryInput, setQueryInput] = useState("");
  const [query, setQuery] = useState("");
  const dialog = useRef<HTMLDialogElement>(null);
  const title = useRef<HTMLHeadingElement>(null);
  const trigger = useRef<HTMLButtonElement>(null);
  const id = useId();
  const count = bookFilterKeys.reduce((sum, key) => sum + applied[key].length, 0);
  const results = filterCoachingBooks(applied, query);
  const draftCount = filterCoachingBooks(draft, query).length;
  const number = (value: number) => value.toLocaleString("de-DE");
  function openFilters() {
    setDraft(copyBookFilters(applied));
    dialog.current?.showModal();
    title.current?.focus();
  }
  function reset() {
    setApplied(emptyBookFilters()); setQuery(""); setQueryInput(""); trigger.current?.focus();
  }
  return <div className="book-search-exercise">
    <div className="book-demo-actions" role="group" aria-label="Übungszustände laden">
      <button type="button" onClick={() => { setApplied(copyBookFilters(targetBookFilters)); setQuery(""); setQueryInput(""); }}>Beispielsuche laden</button>
      <button type="button" onClick={() => { setApplied({ genre: ["Krimi"], trope: ["Enemies to Lovers"], mood: [], rating: [] }); setQuery(""); setQueryInput(""); }}>Nulltreffer ausprobieren</button>
    </div>
    <p className="book-demo-note">Lernbeispiel mit 2.500 synthetischen Datensätzen. Titel und Bewertungen sind erfunden. Die Beispielsuche ergibt konstruktionsbedingt genau 18 Treffer; das ist kein Nutzungs- oder Testergebnis.</p>
    <section className="book-search-preview" aria-labelledby={`${id}-search-title`}>
      <p className="book-search-kicker">DEINE NÄCHSTE GESCHICHTE</p><h3 id={`${id}-search-title`}>Bücher entdecken</h3>
      <form className="book-title-search" onSubmit={event => { event.preventDefault(); setQuery(queryInput.trim()); }}>
        <label htmlFor={`${id}-query`}>Beispieltitel suchen</label><div><input id={`${id}-query`} type="search" value={queryInput} onChange={event => setQueryInput(event.target.value)} placeholder="Zum Beispiel: 0018" /><button type="submit">Suchen</button></div>
      </form>
      <div className="book-filter-toolbar"><span>Schnellauswahl</span><button type="button" ref={trigger} onClick={openFilters} aria-haspopup="dialog" aria-controls={`${id}-dialog`}>Alle Filter ({count})</button></div>
      <div className="book-quick-filters" role="group" aria-label="Quick-Filter">
        {([['genre', 'Romantasy'], ['trope', 'Enemies to Lovers']] as const).map(([key, value]) => <button type="button" key={key} aria-pressed={applied[key].includes(value)} aria-controls={`${id}-results`} onClick={() => setApplied(current => toggleBookFilter(current, key, value))}>{value}{applied[key].includes(value) && <span aria-hidden="true"> ✓</span>}</button>)}
      </div>
      <p className="book-filter-logic">Mehrere Werte innerhalb einer Kategorie: ODER. Zwischen Kategorien und Titelsuche: UND.</p>
      {(count > 0 || query) && <div className="book-active-filters"><h4>Aktiv</h4><ul aria-label="Aktive Filter">
        {bookFilterKeys.flatMap(key => applied[key].map(value => <li key={`${key}-${value}`}><button type="button" aria-label={`${bookFilterOptions[key].label}: ${value} entfernen`} onClick={() => { setApplied(current => toggleBookFilter(current, key, value)); trigger.current?.focus(); }}>{bookFilterOptions[key].label}: {value} <span aria-hidden="true">×</span></button></li>))}
        {query && <li><button type="button" aria-label={`Titelsuche ${query} entfernen`} onClick={() => { setQuery(""); setQueryInput(""); trigger.current?.focus(); }}>Titel: {query} <span aria-hidden="true">×</span></button></li>}
      </ul><button type="button" className="book-reset" onClick={reset}>Alles zurücksetzen</button></div>}
      <p className="book-result-count" role="status" aria-atomic="true">{number(results.length)} {results.length === 1 ? "Buch" : "Bücher"} gefunden{count === 0 && !query ? " · keine Filter aktiv" : ""}</p>
      <div id={`${id}-results`}>
        {results.length ? <><ul className="book-result-list" aria-label="Buchergebnisse">{results.slice(0, 6).map(book => <li key={book.id}><div className="book-cover" aria-hidden="true">{String(book.id).padStart(4, "0")}</div><div><h4>{book.title}</h4><p>{book.genre} · {book.trope}</p><p>{book.mood} · {book.rating.toLocaleString("de-DE")} von 5 Sternen</p></div></li>)}</ul><p className="book-demo-note">Gezeigt: {Math.min(6, results.length)} von {number(results.length)} Treffern. Die Vorschau zeigt höchstens sechs Bücher und hat keine weitere Ergebnisseite.</p></> : <div className="book-empty"><h4>Diese Kombination passt zu keinem Beispielbuch.</h4><p>Entferne oben einen aktiven Filter oder die Titelsuche. Deine Auswahl bleibt bis dahin erhalten.</p><button type="button" onClick={reset}>Suche und Filter zurücksetzen</button></div>}
      </div>
    </section>
    <dialog id={`${id}-dialog`} ref={dialog} className="book-filter-dialog" aria-labelledby={`${id}-dialog-title`} onCancel={() => setDraft(copyBookFilters(applied))}>
      <div className="book-dialog-content"><h3 id={`${id}-dialog-title`} tabIndex={-1} ref={title}>Bücher filtern</h3><p>Änderungen gelten erst nach „Bücher anzeigen“. Abbrechen oder Escape verwirft sie. Die Titelsuche bleibt erhalten.</p><p className="book-filter-logic">ODER innerhalb einer Kategorie, UND zwischen den Kategorien.</p>
        {bookFilterKeys.map(key => <fieldset key={key}><legend>{bookFilterOptions[key].label}</legend>{bookFilterOptions[key].values.map(value => <label key={value}><input type="checkbox" checked={draft[key].includes(value)} onChange={() => setDraft(current => toggleBookFilter(current, key, value))} /><span>{value}</span></label>)}</fieldset>)}
        <button type="button" className="book-reset" onClick={() => setDraft(emptyBookFilters())}>Filterauswahl leeren</button>
        <p className="book-draft-count" role="status" aria-atomic="true">Vorschau: {number(draftCount)} Treffer mit dieser Auswahl.</p>
        <div className="book-dialog-actions"><button type="button" onClick={() => dialog.current?.close()}>Abbrechen</button><button type="button" className="book-apply" onClick={() => { setApplied(copyBookFilters(draft)); dialog.current?.close(); }}>{number(draftCount)} {draftCount === 1 ? "Buch" : "Bücher"} anzeigen</button></div>
      </div>
    </dialog>
  </div>;
}

coaching-book-search.css

.book-search-exercise{max-width:860px;margin-top:24px;min-width:0}
.book-demo-actions{display:flex;flex-wrap:wrap;gap:10px}
.book-demo-actions button,.book-search-preview button,.book-filter-dialog button{min-height:44px;padding:10px 14px;border:1px solid #758198;border-radius:7px;background:white;font-size:13px;line-height:1.6;color:#23334f;overflow-wrap:anywhere}
.lesson-content .book-demo-note{font-size:12px;line-height:1.8;color:#536174;margin-top:16px}
.book-search-preview{max-width:375px;padding:22px;margin-top:24px;border:1px solid #bcc9df;background:#f6f8fc;border-radius:14px;min-width:0}
.lesson-content .book-search-kicker{font:9px/1.8 monospace;letter-spacing:.08em;color:#536174;margin:0}
.lesson-content .book-search-preview h3{font:500 27px/1.3 system-ui,sans-serif;margin:10px 0 24px}
.book-title-search label{font-size:12px;line-height:1.7;color:#44526a;display:block;margin-bottom:8px}
.book-title-search>div{display:flex;flex-wrap:wrap;gap:8px}
.book-title-search input{flex:1 1 130px;min-width:0;width:100%;min-height:44px;padding:10px;border:1px solid #758198;border-radius:7px;font-size:14px;background:white}
.book-filter-toolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;margin:24px 0 12px}
.book-filter-toolbar>span{font-size:12px;color:#536174}
.book-quick-filters{display:flex;flex-wrap:wrap;gap:8px}
.book-quick-filters button[aria-pressed=true]{border-color:#2558ed;background:#e5edff;color:#153eab;font-weight:600}
.lesson-content .book-filter-logic{font-size:12px;line-height:1.8;color:#536174;margin-top:16px}
.book-active-filters{border-top:1px solid #ccd5e5;margin-top:20px;padding-top:16px}
.lesson-content .book-active-filters h4{font-size:13px;font-weight:600;margin:0 0 10px}
.book-active-filters ul{list-style:none;padding:0;margin:0;display:flex;flex-wrap:wrap;gap:8px}
.book-active-filters li{max-width:100%;min-width:0}
.book-active-filters li button{max-width:100%;text-align:left;background:#e7edf9;border-color:#b4c4e3;font-size:12px}
.book-active-filters li button span{font-size:18px;margin-left:6px}
.book-search-preview button.book-reset,.book-filter-dialog button.book-reset{border:0;background:transparent;padding-left:0;text-decoration:underline;text-underline-offset:4px;margin-top:8px;text-align:left}
.lesson-content .book-result-count{font-size:16px;line-height:1.6;font-weight:600;margin-top:24px;color:#203455}
.book-result-list{list-style:none;padding:0;margin:16px 0 0}
.book-result-list li{display:flex;align-items:flex-start;gap:14px;border-top:1px solid #d0d9e8;padding:18px 0}
.book-result-list li>div:last-child{min-width:0;overflow-wrap:anywhere}
.book-cover{display:grid;place-items:center;width:44px;min-height:64px;flex-shrink:0;background:#dbe5fc;border-left:4px solid #6988cf;color:#274b99;font:10px/1.5 monospace;border-radius:2px 5px 5px 2px}
.lesson-content .book-result-list h4{font:600 14px/1.6 system-ui,sans-serif;margin:0}
.lesson-content .book-result-list p{font-size:12px;line-height:1.8;color:#536174;margin-top:5px}
.book-empty{padding:20px;border:1px solid #ccd5e5;border-radius:8px;background:white;margin-top:16px}
.lesson-content .book-empty h4{font-size:17px;line-height:1.5;margin:0}
.lesson-content .book-empty p{font-size:13px;line-height:1.8;margin:12px 0}
.book-filter-dialog{position:fixed;inset:0;margin:auto;width:440px;max-width:calc(100vw - 32px);max-height:calc(100dvh - 32px);padding:0;border:1px solid #9daecb;border-radius:14px;background:white;color:#172131;overflow:auto;overscroll-behavior:contain}
.book-filter-dialog::backdrop{background:#101a30a6}
.book-dialog-content{padding:24px}
.book-dialog-content h3{font:500 26px/1.4 system-ui,sans-serif;margin:0;scroll-margin-top:20px}
.book-dialog-content p{font-size:13px;line-height:1.8;color:#536174;margin-top:12px}
.book-dialog-content fieldset{border:0;border-top:1px solid #d0d9e8;padding:16px 0 0;margin-top:24px;min-width:0}
.book-dialog-content legend{padding-right:12px;font-size:15px;line-height:1.7;font-weight:600}
.book-dialog-content label{display:flex;align-items:center;gap:12px;min-height:44px;font-size:14px;line-height:1.7;cursor:pointer;padding:8px 0;overflow-wrap:anywhere}
.book-dialog-content input{width:18px;height:18px;flex-shrink:0;accent-color:#2558ed}
.book-dialog-actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:20px}
.book-dialog-actions button{flex:1 1 140px}
.book-dialog-actions button.book-apply{background:#2558ed;color:white;border-color:#2558ed}
.book-search-exercise :is(button,input,summary):focus-visible,.book-filter-dialog :is(button,input,h3):focus-visible{outline:3px solid #2558ed;outline-offset:3px}
@media(max-width:640px){.book-search-preview{padding:16px}.book-dialog-content{padding:20px}}

coaching-book-filters.ts

export const bookFilterOptions = {
  genre: { label: "Genre", values: ["Romantasy", "Contemporary Romance", "Krimi"] },
  trope: { label: "Trope", values: ["Enemies to Lovers", "Found Family", "Slow Burn"] },
  mood: { label: "Stimmung", values: ["Spannend", "Ruhig"] },
  rating: { label: "Bewertung", values: ["Ab 4 Sternen"] },
} as const;
export type BookFilterKey = keyof typeof bookFilterOptions;
export type BookFilters = Record<BookFilterKey, string[]>;
export type CoachingBook = { id: number; title: string; genre: string; trope: string; mood: string; rating: number };
export const bookFilterKeys: BookFilterKey[] = ["genre", "trope", "mood", "rating"];
export function emptyBookFilters(): BookFilters { return { genre: [], trope: [], mood: [], rating: [] }; }
export function copyBookFilters(filters: BookFilters): BookFilters {
  return { genre: [...filters.genre], trope: [...filters.trope], mood: [...filters.mood], rating: [...filters.rating] };
}
export function toggleBookFilter(filters: BookFilters, key: BookFilterKey, value: string): BookFilters {
  return { ...filters, [key]: filters[key].includes(value) ? filters[key].filter(item => item !== value) : [...filters[key], value] };
}
export const targetBookFilters: BookFilters = { genre: ["Romantasy"], trope: ["Enemies to Lovers"], mood: ["Spannend"], rating: ["Ab 4 Sternen"] };
// Synthetic fixtures, not a real catalogue or product usage data.
const fixtureGroups = [
  { genre: "Romantasy", trope: "Found Family", mood: "Spannend", rating: 4 },
  { genre: "Romantasy", trope: "Enemies to Lovers", mood: "Ruhig", rating: 4.1 },
  { genre: "Contemporary Romance", trope: "Slow Burn", mood: "Spannend", rating: 4.2 },
  { genre: "Krimi", trope: "Found Family", mood: "Ruhig", rating: 3.8 },
];
export const coachingBooks: CoachingBook[] = Array.from({ length: 2500 }, (_, index) => ({
  id: index + 1,
  title: `Beispielbuch ${String(index + 1).padStart(4, "0")}`,
  ...(index < 18 ? { genre: "Romantasy", trope: "Enemies to Lovers", mood: "Spannend", rating: 4.5 } : fixtureGroups[(index - 18) % fixtureGroups.length]),
}));
export function filterCoachingBooks(filters: BookFilters, query = ""): CoachingBook[] {
  const search = query.trim().toLocaleLowerCase("de");
  return coachingBooks.filter(book => (!search || book.title.toLocaleLowerCase("de").includes(search)) && bookFilterKeys.every(key =>
    filters[key].length === 0 || filters[key].some(value => key === "rating" ? book.rating >= 4 : book[key] === value)
  ));
}

04 / DIE LOGIK HINTER DEN CHIPS

ODER innerhalb, UND dazwischen.

Wählst du „Romantasy“ und „Contemporary Romance“, zeigt die Musterlösung Bücher aus mindestens einem dieser Genres: ODER. Ergänzt du „Spannend“, müssen sie außerdem diese Stimmung erfüllen: UND. Die Titelsuche schränkt das Ergebnis zusätzlich ein.

Eine leere Kategorie schränkt die Suche nicht ein. „Ab 4 Sternen“ schließt vier Sterne ein. Keine dieser Regeln ist für jedes Produkt automatisch richtig; sie muss zur Bedeutung der Kategorien passen und konsistent erklärt werden. Die Beispieldaten vergeben pro Buch jeweils einen Wert für Genre, Trope und Stimmung. Ein echter Katalog kann mehrere Werte pro Buch besitzen und braucht dann ein entsprechend erweitertes Datenmodell.

Technik unterstützt die Interaktion.

Der Ergebniszähler verwendet role="status" und aria-atomic="true". So kann eine veränderte Trefferzahl ohne Fokuswechsel angekündigt werden. Die Titelsuche reagiert hier erst auf Absenden; bei einer asynchronen Suche müsstest du außerdem Eingaben bündeln, veraltete Antworten verwerfen sowie Lade- und Fehlerzustände gestalten. W3C: Statusmeldungen mit ARIA22.

Der native Dialog wird mit showModal() geöffnet. Der Fokus geht zur Überschrift; Tab bleibt im Dialog, Escape schließt ihn und anschließend kehrt der Fokus zum Auslöser zurück. Der Hintergrund ist währenddessen nicht bedienbar. Prüfe diese Abläufe in deinen Zielbrowsern und mit den vorgesehenen Hilfsmitteln. W3C: Modale Dialoge mit HTML, ARIA APG: Dialog Pattern.

05 / REFLEXION · 2 MINUTEN

Welche Kontrolle hilft bei dieser Suche?

Sofort aktualisieren oder gesammelt anwenden?

Wie würdest du Filter bei mobilen Suchaufgaben anwenden? Berücksichtige Ladezeiten, mehrere Änderungen hintereinander und die Rückmeldung zum Ergebnis.

Mögliche Antwort zum Anwenden

Für einzelne, leicht umkehrbare Quick-Filter würde ich eine sofortige Aktualisierung prüfen. Bei mehreren Kriterien im Dialog hilft eine gesammelte Übernahme, die Auswahl in Ruhe zusammenzustellen. Eine Vorschau wie „18 Bücher anzeigen“ erklärt das erwartete Ergebnis. Bei langsamer Suche darf die Zahl erst erscheinen, wenn sie zur aktuellen Auswahl gehört.

Ich würde beobachten, ob Menschen nach einer Änderung verstehen, was schon angewendet wurde, ob sie ihre Suche erfolgreich abschließen und wie häufig sie den Dialog erneut öffnen. Ladezeiten und Zahl der nötigen Interaktionen gehören zusammen betrachtet.

Was machst du mit 40 Tropes?

Alle als Chips, eine durchsuchbare Liste oder häufig verwendete Optionen zuerst? Welche Informationen brauchst du für die Entscheidung?

Mögliche Antwort zu vielen Optionen

Eine durchsuchbare Mehrfachauswahl im Dialog wäre ein plausibler Ausgangspunkt. Häufige Optionen können zusätzlich sichtbar sein, wenn Suchaufgaben und Nutzung das stützen. Ausgewählte Werte bleiben auch bei einer Suche innerhalb der Optionen auffindbar; das Leeren dieser Optionssuche entfernt keine Auswahl.

Ich brauche Hinweise darauf, ob Menschen Tropes beim Namen kennen oder erst entdecken, welche Begriffe sie verwenden, welche Kombinationen häufig sind und wie oft sie seltene Optionen benötigen. Für Einsteiger können kurze Erklärungen hilfreicher sein als eine nur alphabetische Liste. Die Musterlösung zeigt bewusst nur drei Tropes und setzt diese Erweiterung noch nicht um.

Dein Selbstcheck

  • Kannst du jeden aktiven Filter erkennen und einzeln entfernen?
  • Bleiben Dialogentwurf und angewendete Auswahl klar unterscheidbar?
  • Erhält Abbrechen alle bisher angewendeten Einstellungen?
  • Sind Filterlogik, Trefferzahl und der Weg aus null Treffern nachvollziehbar?
  • Funktionieren Öffnen, Tab, Escape und Fokusrückkehr mit der Tastatur?

DEIN MERKSATZ

Weniger Aufwand bis zum relevanten Ergebnis.

Ein gutes Filtersystem maximiert nicht die Anzahl der Einstellungsmöglichkeiten. Es macht verständlich, wie eine Auswahl wirkt und wie Menschen sie gezielt verändern können.