Komponente mit Mehrfachauswahl erstellen

Eine grundlegende Übersicht dazu, wie Sie eine responsive, adaptive und barrierefreie Mehrfachauswahlkomponente zum Sortieren und Filtern von Inhalten erstellen.

In diesem Beitrag möchte ich meine Gedanken zu einer Möglichkeit zur Erstellung einer Mehrfachauswahlkomponente teilen. Demo ansehen

Demo

Wenn du lieber ein Video ansiehst, findest du hier eine YouTube-Version dieses Beitrags:

Übersicht

Nutzern werden oft Elemente angezeigt, manchmal sehr viele. In diesen Fällen kann es sinnvoll sein, eine Möglichkeit zur Reduzierung der Liste anzubieten, um eine Überforderung durch zu viele Optionen zu vermeiden. In diesem Blogpost wird die Filteroberfläche als Möglichkeit zur Reduzierung der Auswahlmöglichkeiten untersucht. Dazu werden Artikelattribute angezeigt, die Nutzer auswählen oder deaktivieren können. So werden die Ergebnisse reduziert und die Auswahl übersichtlicher.

Interaktionen

Ziel ist es, allen Nutzern und ihren unterschiedlichen Eingabetypen eine schnelle Navigation durch die Filteroptionen zu ermöglichen. Dazu werden zwei anpassbare und responsive Komponenten verwendet. Eine traditionelle Seitenleiste mit Kästchen für Desktop-Computer, Tastaturen und Screenreader sowie ein <select multiple> für Touchbildschirme.

Vergleichsscreenshot, der die Desktopversion in hellem und dunklem Modus mit einer Seitenleiste mit Kästchen und die mobilen Versionen für iOS und Android mit einem Element für die Mehrfachauswahl zeigt

Die Entscheidung, die integrierte Mehrfachauswahl für Touchbedienung und nicht für Desktop-Computer zu verwenden, spart Arbeit und schafft Arbeit. Ich glaube jedoch, dass sie eine angemessene Benutzererfahrung mit weniger Code-Schulden bietet, als die gesamte responsive Benutzeroberfläche in einer Komponente zu erstellen.

Berührung

Die Touch-Komponente spart Platz und trägt dazu bei, die Genauigkeit der Nutzerinteraktion auf Mobilgeräten zu verbessern. So wird Platz gespart, da eine ganze Seitenleiste mit Kästchen in ein integriertes<select> Touch-Overlay minimiert wird. Sie trägt zur Eingabegenauigkeit bei, da das System ein großes Touch-Overlay anzeigt.

Screenshot-Vorschau des Elements für die Mehrfachauswahl in Chrome auf Android-Geräten, iPhones und iPads Auf dem iPad und dem iPhone ist die Mehrfachauswahl aktiviert. Die Benutzeroberfläche ist für die jeweilige Bildschirmgröße optimiert.

Tastatur und Gamepad

Unten sehen Sie eine Demonstration, wie Sie <select multiple> über die Tastatur verwenden.

Diese integrierte Mehrfachauswahl kann nicht formatiert werden und ist nur in einem kompakten Layout verfügbar, das sich nicht für die Darstellung vieler Optionen eignet. Sehen Sie, wie Sie in diesem winzigen Feld nicht wirklich die Vielfalt der Optionen sehen können? Sie können die Größe zwar ändern, aber sie ist immer noch nicht so nutzerfreundlich wie eine Seitenleiste mit Kästchen.

Markieren & Zeichnen

Beide Komponenten befinden sich im selben <form>-Element. Die Ergebnisse dieses Formulars, ob Kästchen oder Mehrfachauswahl, werden beobachtet und zum Filtern des Rasters verwendet, können aber auch an einen Server gesendet werden.

<form>

</form>

Komponente „Kästchen“

Gruppen von Kästchen sollten in einem <fieldset>-Element eingeschlossen und mit einem <legend> versehen werden. Wenn HTML so strukturiert ist, können Screenreader und FormData die Beziehung der Elemente automatisch erkennen.

<form>
  <fieldset>
    <legend>New</legend>
    … checkboxes …
  </fieldset>
</form>

Fügen Sie nach der Gruppierung für jeden Filter ein <label> und ein <input type="checkbox"> hinzu. Ich habe meine in einen <div>-Block gepackt, damit die CSS-Eigenschaft gap sie gleichmäßig platzieren und die Ausrichtung beibehalten kann, wenn Labels mehrzeilig sind.

<form>
  <fieldset>
    <legend>New</legend>
    <div>
      <input type="checkbox" id="last 30 days" name="new" value="last 30 days">
      <label for="last 30 days">Last 30 Days</label>
    </div>
    <div>
      <input type="checkbox" id="last 6 months" name="new" value="last 6 months">
      <label for="last 6 months">Last 6 Months</label>
    </div>
   </fieldset>
</form>

Ein Screenshot mit einem informativen Overlay für die Legende und die Feldsatzelemente, die Farbe und Elementnamen zeigt.

<select multiple>-Komponente

Eine selten verwendete Funktion des <select>-Elements ist multiple. Wenn das Attribut mit einem <select>-Element verwendet wird, kann der Nutzer mehrere Elemente aus der Liste auswählen. Das ist vergleichbar mit dem Wechsel von einer Auswahlliste zu einer Kästchenliste.

<form>
  <select multiple="true" title="Filter results by category">
    …
  </select>
</form>

Wenn du innerhalb eines <select>-Elements Gruppen labeln und erstellen möchtest, verwende das Element <optgroup> und gib ihm ein label-Attribut und einen label-Wert. Dieses Element und dieses Attribut ähneln den Elementen <fieldset> und <legend>.

<form>
  <select multiple="true" title="Filter results by category">
    <optgroup label="New">
      …
    </optgroup>
  </select>
</form>

Fügen Sie nun die <option>-Elemente für den Filter hinzu.

<form>
  <select multiple="true" title="Filter results by category">
    <optgroup label="New">
      <option value="last 30 days">Last 30 Days</option>
      <option value="last 6 months">Last 6 Months</option>
    </optgroup>
  </select>
</form>

Screenshot des Desktop-Renderings eines Elements mit Mehrfachauswahl

Eingaben mit Zählern erfassen, um Hilfstechnologien zu informieren

Bei dieser Nutzererfahrung wird die Statusrolle verwendet, um die Anzahl der Filter für Screenreader und andere Hilfstechnologien zu erfassen und zu verwalten. In diesem YouTube-Video wird die Funktion veranschaulicht. Die Integration beginnt mit HTML und dem Attribut role="status".

<div role="status" class="sr-only" id="applied-filters"></div>

Mit diesem Element werden Änderungen am Inhalt vorgelesen. Wir können den Inhalt mit CSS-Zählern aktualisieren, wenn Nutzer mit den Kästchen interagieren. Dazu müssen wir zuerst einen Zähler mit einem Namen in einem übergeordneten Element der Eingaben und des Statuselements erstellen.

aside {
  counter-reset: filters;
}

Standardmäßig ist die Anzahl 0. Das ist gut, da in diesem Design standardmäßig nichts :checked ist.

Als Nächstes richten wir das Ziel auf untergeordnete Elemente des Elements <aside> aus, die :checked sind, um den neu erstellten Zähler zu erhöhen. Wenn der Nutzer den Status der Eingaben ändert, wird der filters-Zähler erhöht.

aside :checked {
  counter-increment: filters;
}

CSS kennt jetzt die allgemeine Gesamtzahl der Kästchen-UI und das Status-Rollenelement ist leer und wartet auf Werte. Da die Zählung in CSS im Arbeitsspeicher gespeichert wird, kann über die Funktion counter() auf den Wert aus dem Inhalt des Pseudoelements zugegriffen werden:

aside #applied-filters::before {
  content: counter(filters) " filters ";
}

Die HTML-Datei für das Statuselement für die Rolle liest jetzt „2 Filter“ für einen Screenreader vor. Das ist ein guter Anfang, aber wir können noch besser werden. Zum Beispiel könnten wir die Anzahl der Ergebnisse anzeigen, die durch die Filter aktualisiert wurden. Wir führen diese Arbeit in JavaScript aus, da Zähler dafür nicht geeignet sind.

Screenshot des MacOS-Screenreaders, der die Anzahl der aktiven Filter ansagt

Nesting-Aufregung

Der Zähleralgorithmus funktionierte mit CSS-Verschachtelung 1 hervorragend, da ich die gesamte Logik in einen Block einfügen konnte. Sie ist portabel und zentralisiert für das Lesen und Aktualisieren.

aside {
  counter-reset: filters;

  & :checked {
    counter-increment: filters;
  }

  & #applied-filters::before {
    content: counter(filters) " filters ";
  }
}

Layouts

In diesem Abschnitt werden die Layouts zwischen den beiden Komponenten beschrieben. Die meisten Layoutstile sind für die Kästchenkomponente auf dem Computer gedacht.

Das Formular

Um die Lesbarkeit und Übersichtlichkeit für Nutzer zu optimieren, hat das Formular eine maximale Breite von 30 Zeichen. Damit wird im Grunde eine optische Zeilenbreite für jedes Filterlabel festgelegt. Im Formular wird das Rasterlayout und das Attribut gap verwendet, um die Feldgruppen zu verteilen.

form {
  display: grid;
  gap: 2ch;
  max-inline-size: 30ch;
}

Das <select>-Element

Die Liste der Labels und Kästchen belegen auf Mobilgeräten zu viel Platz. Daher wird im Layout das primäre Eingabegerät des Nutzers geprüft, um die Oberfläche für Touchbedienung anzupassen.

@media (pointer: coarse) {
  select[multiple] {
    display: block;
  }
}

Ein Wert von coarse gibt an, dass der Nutzer mit seinem primären Eingabegerät nicht sehr präzise mit dem Display interagieren kann. Auf einem Mobilgerät ist der Zeigerwert oft coarse, da die primäre Interaktion das Tippen ist. Auf einem Desktop-Gerät ist der Cursorwert häufig fine, da in der Regel eine Maus oder ein anderes Eingabegerät mit hoher Präzision angeschlossen ist.

Die fieldsets

Das Standard-Styling und -Layout eines <fieldset> mit einem <legend> ist einzigartig:

Screenshot der Standardstile für ein Feldsatz und eine Legende

Normalerweise verwende ich die Eigenschaft gap, um die Abstände zwischen meinen untergeordneten Elementen zu steuern. Aufgrund der einzigartigen Positionierung des <legend> ist es jedoch schwierig, eine gleichmäßig verteilte Gruppe von untergeordneten Elementen zu erstellen. Anstelle von gap werden die adjacent sibling-Auswahl und margin-block-start verwendet.

fieldset {
  padding: 2ch;

  & > div + div {
    margin-block-start: 2ch;
  }
}

Dadurch wird verhindert, dass der Bereich für <legend> angepasst wird, da nur die untergeordneten Elemente <div> ausgerichtet werden.

Screenshot, der den Abstand zwischen den Eingaben, aber nicht die Legende zeigt

Filterlabel und Kästchen

Da es sich um ein direkt untergeordnetes Element eines <fieldset> handelt und die maximale Breite des 30ch des Formulars nicht überschritten wird, wird der Labeltext möglicherweise umgebrochen, wenn er zu lang ist. Textumbruch ist in Ordnung, aber eine Fehlausrichtung zwischen Text und Kästchen ist nicht gut. Flexbox eignet sich dafür hervorragend.

fieldset > div {
  display: flex;
  gap: 2ch;
  align-items: baseline;
}
Screenshot, der zeigt, wie das Häkchen bei einem Umbruch mit mehreren Zeilen an der ersten Textzeile ausgerichtet ist
Weitere Spiele in diesem Codepen

Das animierte Raster

Die Layoutanimation wird von Isotope erstellt. Ein leistungsstarkes Plug-in für die interaktive Sortierung und Filterung.

JavaScript

JavaScript wird nicht nur verwendet, um ein ansprechendes animiertes, interaktives Raster zu erstellen, sondern auch, um einige Ecken und Kanten zu glätten.

Normalisierung der Nutzereingabe

Dieses Design hat ein Formular mit zwei verschiedenen Eingabemöglichkeiten, die nicht serialisiert werden. Mit etwas JavaScript können wir die Daten jedoch normalisieren.

Screenshot der JavaScript-Konsole der Entwicklertools mit den Ergebnissen für das Zielvorhaben und normalisierte Daten

Ich habe die Datenstruktur des <select>-Elements an die Struktur der gruppierten Kästchen angepasst. Dazu wird dem <select>-Element ein input-Ereignis-Listener hinzugefügt, wodurch selectedOptions zugeordnet werden.

document.querySelector('select').addEventListener('input', event => {
  // make selectedOptions iterable then reduce a new array object
  let selectData = Array.from(event.target.selectedOptions).reduce((data, opt) => {
    // parent optgroup label and option value are added to the reduce aggregator
    data.push([opt.parentElement.label.toLowerCase(), opt.value])
    return data
  }, [])
})

Jetzt können Sie das Formular absenden oder in diesem Fall Isotope anweisen, nach welchen Kriterien gefiltert werden soll.

Statusrollenelement fertigstellen

Das Element zählt und anzeigt nur die Filteranzahl basierend auf der Kästcheninteraktion. Ich fand es jedoch sinnvoll, zusätzlich die Anzahl der Ergebnisse anzugeben und dafür zu sorgen, dass auch die Auswahlmöglichkeiten des <select>-Elements gezählt werden.

<select>-Elementauswahl, die sich im counter() widerspiegelt

Im Abschnitt zur Datennormalisierung wurde bereits ein Listener für die Eingabe erstellt. Am Ende dieser Funktion sind die Anzahl der ausgewählten Filter und die Anzahl der Ergebnisse für diese Filter bekannt. Die Werte können so an das Element „state role“ übergeben werden.

let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length

Ergebnisse, die im Element role="status" berücksichtigt werden

:checked bietet eine integrierte Möglichkeit, die Anzahl der ausgewählten Filter an das Status-Rollenelement weiterzugeben. Die gefilterte Anzahl der Ergebnisse ist jedoch nicht sichtbar. JavaScript kann auf Interaktionen mit den Kästchen achten und nach dem Filtern des Rasters textContent hinzufügen, ähnlich wie das <select>-Element.

document
  .querySelector('aside form')
  .addEventListener('input', e => {
    // isotope demo code
    let filterResults = IsotopeGrid.getFilteredItemElements().length
    document.querySelector('#applied-filters').textContent = `giving ${filterResults} results`
})

Zusammengenommen vervollständigt diese Arbeit die Ankündigung „2 Filter mit 25 Ergebnissen“.

Screenshot des MacOS-Screenreaders, der Ergebnisse ansagt

Jetzt können alle Nutzer unsere hervorragenden Hilfstechnologien nutzen, unabhängig davon, wie sie damit interagieren.

Fazit

Wie würden Sie das machen?

Lassen Sie uns unsere Ansätze diversifizieren und alle Möglichkeiten kennenlernen, wie Sie im Web entwickeln können. Erstelle eine Demo, tweete mir Links und ich füge sie unten in den Abschnitt „Community-Remixe“ hinzu.

Remixe der Community

Noch keine Aktivität hierzu.