Membuat komponen multi-pilihan

Ringkasan dasar tentang cara membuat komponen multi-pilihan yang responsif, adaptif, dan mudah diakses untuk pengalaman pengguna pengurutan dan pemfilteran.

Dalam postingan ini, saya ingin membagikan pemikiran tentang cara membuat komponen multi-pilih. Coba demo.

Demo

Jika Anda lebih suka video, berikut versi YouTube dari postingan ini:

Ringkasan

Pengguna sering kali melihat item, terkadang banyak item, dan dalam kasus ini, sebaiknya berikan cara untuk mengurangi daftar guna mencegah kelebihan pilihan. Postingan blog ini mengeksplorasi UI pemfilteran sebagai cara untuk mengurangi pilihan. Hal ini dilakukan dengan menampilkan atribut item yang dapat dipilih atau dibatalkan pilihannya oleh pengguna, sehingga mengurangi hasil dan mengurangi kelebihan pilihan.

Interaksi

Tujuannya adalah untuk memungkinkan penelusuran cepat opsi filter bagi semua pengguna dan berbagai jenis input mereka. Hal ini akan dikirimkan dengan sepasang komponen yang dapat disesuaikan dan responsif. Sidebar kotak centang tradisional untuk desktop, keyboard, dan pembaca layar, serta <select multiple> untuk pengguna sentuh.

Screenshot perbandingan yang menampilkan desktop terang dan gelap dengan sidebar kotak centang
vs iOS dan Android seluler dengan elemen multi-pilih.

Keputusan untuk menggunakan multiseleksi bawaan untuk perangkat sentuh, dan bukan untuk desktop, menghemat pekerjaan dan menciptakan pekerjaan, tetapi saya yakin memberikan pengalaman yang sesuai dengan lebih sedikit utang kode daripada mem-build seluruh pengalaman responsif dalam satu komponen.

Sentuh

Komponen sentuh menghemat ruang dan membantu akurasi interaksi pengguna di perangkat seluler. Hal ini menghemat ruang dengan menciutkan seluruh sidebar kotak centang ke dalam pengalaman sentuh overlay bawaan <select>. Hal ini membantu akurasi input dengan menampilkan pengalaman overlay sentuh besar yang disediakan oleh sistem.

Pratinjau
screenshot elemen multi-pilih di Chrome pada Android, iPhone, dan
iPad. iPad dan iPhone memiliki tombol multi-pilih yang terbuka, dan masing-masing mendapatkan
pengalaman unik yang dioptimalkan untuk ukuran layar.

Keyboard dan gamepad

Berikut adalah demonstrasi cara menggunakan <select multiple> dari keyboard.

Multi-pilih bawaan ini tidak dapat diberi gaya dan hanya ditawarkan dalam tata letak ringkas yang tidak cocok untuk menampilkan banyak opsi. Lihat bagaimana Anda tidak dapat benar-benar melihat ragam opsi di kotak kecil tersebut? Meskipun Anda dapat mengubah ukurannya, kolom ini masih tidak dapat digunakan seperti sidebar kotak centang.

Markup

Kedua komponen akan berada dalam elemen <form> yang sama. Hasil formulir ini, baik kotak centang maupun multi-pilih, akan diamati dan digunakan untuk memfilter petak, tetapi juga dapat dikirim ke server.

<form>

</form>

Komponen kotak centang

Grup kotak centang harus digabungkan dalam elemen <fieldset> dan diberi <legend>. Jika HTML disusun dengan cara ini, pembaca layar dan FormData akan otomatis memahami hubungan elemen.

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

Dengan pengelompokan yang sudah diterapkan, tambahkan <label> dan <input type="checkbox"> untuk setiap filter. Saya memilih untuk menggabungkannya dalam <div> sehingga properti gap CSS dapat mengatur spasi secara merata dan mempertahankan perataan saat label menjadi beberapa baris.

<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>

Screenshot dengan overlay informatif untuk elemen legenda dan
  fieldset, menampilkan warna dan nama elemen.

<select multiple> komponen

Fitur elemen <select> yang jarang digunakan adalah multiple. Jika atribut digunakan dengan elemen <select>, pengguna diizinkan untuk memilih banyak dari daftar. Ini seperti mengubah interaksi dari daftar pilihan menjadi daftar kotak centang.

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

Untuk memberi label dan membuat grup di dalam <select>, gunakan elemen <optgroup> dan beri atribut dan nilai label. Elemen dan nilai atribut ini mirip dengan elemen <fieldset> dan <legend>.

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

Sekarang, tambahkan elemen <option> untuk filter.

<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 rendering desktop elemen multi-pilih.

Melacak input dengan penghitung untuk menginformasikan teknologi pendukung

Teknik status role digunakan dalam pengalaman pengguna ini, untuk melacak dan mempertahankan jumlah filter untuk pembaca layar dan teknologi pendukung lainnya. Video YouTube menunjukkan fitur ini. Integrasi dimulai dengan HTML dan atribut role="status".

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

Elemen ini akan membacakan perubahan yang dibuat pada konten. Kita dapat memperbarui konten dengan penghitung CSS saat pengguna berinteraksi dengan kotak centang. Untuk melakukannya, pertama-tama kita harus membuat penghitung dengan nama pada elemen induk input dan elemen status.

aside {
  counter-reset: filters;
}

Secara default, jumlahnya akan menjadi 0, yang bagus, tidak ada yang :checked secara default dalam desain ini.

Selanjutnya, untuk menambahkan penghitung yang baru dibuat, kita akan menargetkan turunan elemen <aside> yang merupakan :checked. Saat pengguna mengubah status input, penghitung filters akan dijumlahkan.

aside :checked {
  counter-increment: filters;
}

CSS kini mengetahui jumlah umum UI kotak centang dan elemen peran status kosong serta menunggu nilai. Karena CSS mempertahankan penghitungan dalam memori, fungsi counter() memungkinkan akses ke nilai dari konten elemen pseudo:

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

HTML untuk elemen peran status kini akan mengumumkan "2 filter " ke pembaca layar. Ini adalah awal yang baik, tetapi kita bisa melakukannya dengan lebih baik, seperti membagikan jumlah hasil yang telah diperbarui filter. Kita akan melakukan pekerjaan ini dari JavaScript, karena hal ini berada di luar kemampuan penghitung.

Screenshot pembaca layar MacOS yang mengumumkan jumlah filter aktif.

Antusiasme bertingkat

Algoritme penghitung terasa sangat cocok dengan CSS nesting-1, karena saya dapat menempatkan semua logika ke dalam satu blok. Terasa portabel dan terpusat untuk membaca dan memperbarui.

aside {
  counter-reset: filters;

  & :checked {
    counter-increment: filters;
  }

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

Tata letak

Bagian ini menjelaskan tata letak antara kedua komponen. Sebagian besar gaya tata letak ditujukan untuk komponen kotak centang desktop.

Formulir

Untuk mengoptimalkan keterbacaan dan keterpindaian bagi pengguna, formulir diberi lebar maksimum 30 karakter, yang pada dasarnya menetapkan lebar baris optik untuk setiap label filter. Formulir menggunakan tata letak petak dan properti gap untuk mengatur jarak fieldset.

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

Elemen <select>

Daftar label dan kotak centang menghabiskan terlalu banyak ruang di perangkat seluler. Oleh karena itu, tata letak memeriksa untuk melihat perangkat penunjuk utama pengguna guna mengubah pengalaman untuk sentuhan.

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

Nilai coarse menunjukkan bahwa pengguna tidak akan dapat berinteraksi dengan layar dengan presisi tinggi menggunakan perangkat input utama mereka. Pada perangkat seluler, nilai pointer sering kali coarse, karena interaksi utama adalah sentuh. Di perangkat desktop, nilai pointer sering kali fine karena mouse atau perangkat input presisi tinggi lainnya sering kali terhubung.

Set data

Gaya dan tata letak default <fieldset> dengan <legend> bersifat unik:

Screenshot gaya default untuk fieldset dan legenda.

Biasanya, untuk memberi spasi pada elemen turunan, saya akan menggunakan properti gap, tetapi posisi unik <legend> mempersulit pembuatan kumpulan turunan yang berjarak sama. Sebagai ganti gap, pemilih saudara terdekat dan margin-block-start digunakan.

fieldset {
  padding: 2ch;

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

Tindakan ini akan melewati <legend> agar ruangnya tidak disesuaikan dengan hanya menargetkan turunan <div>.

Screenshot yang menunjukkan spasi margin di antara input, tetapi tidak menunjukkan legenda.

Label filter dan kotak centang

Sebagai turunan langsung dari <fieldset> dan dalam lebar maksimum 30ch formulir, teks label dapat digabungkan jika terlalu panjang. Penggabungan teks itu bagus, tetapi ketidakselarasan antara teks dan kotak centang tidak. Flexbox sangat cocok untuk hal ini.

fieldset > div {
  display: flex;
  gap: 2ch;
  align-items: baseline;
}
Screenshot yang menunjukkan cara tanda centang disejajarkan dengan
    baris pertama teks dalam skenario penggabungan multibaris.
Bermain lebih banyak di Codepen ini

Petak animasi

Animasi tata letak dilakukan oleh Isotope. Plugin yang berperforma tinggi dan andal untuk pengurutan dan pemfilteran interaktif.

JavaScript

Selain membantu mengatur petak interaktif animasi yang rapi, JavaScript digunakan untuk memoles beberapa kekurangan.

Mennormalisasi input pengguna

Desain ini memiliki satu formulir dengan dua cara berbeda untuk memberikan input, dan keduanya tidak melakukan serialisasi yang sama. Namun, dengan beberapa JavaScript, kita dapat menormalisasi data.

Screenshot konsol JavaScript DevTools yang menampilkan sasaran, hasil data yang dinormalisasi.

Saya memilih untuk menyelaraskan struktur data elemen <select> dengan struktur kotak centang yang dikelompokkan. Untuk melakukannya, pemroses peristiwa input ditambahkan ke elemen <select>, pada saat itu selectedOptions dipetakan.

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
  }, [])
})

Sekarang Anda dapat mengirimkan formulir, atau dalam kasus demo ini, beri tahu Isotope apa yang akan difilter.

Menyelesaikan elemen peran status

Elemen ini hanya menghitung dan mengumumkan jumlah filter berdasarkan interaksi kotak centang, tetapi saya merasa sebaiknya juga membagikan jumlah hasil dan memastikan pilihan elemen <select> juga dihitung.

Pilihan elemen <select> tercermin dalam counter()

Di bagian normalisasi data, pemroses sudah dibuat pada input. Di akhir fungsi ini, jumlah filter yang dipilih dan jumlah hasil untuk filter tersebut diketahui. Nilai dapat diteruskan ke elemen peran status seperti ini.

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

Hasil yang tercermin dalam elemen role="status"

:checked menyediakan cara bawaan untuk meneruskan jumlah filter yang dipilih ke elemen peran status, tetapi tidak memiliki visibilitas ke jumlah hasil yang difilter. JavaScript dapat memantau interaksi dengan kotak centang dan setelah memfilter grid, menambahkan textContent seperti yang dilakukan elemen <select>.

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

Secara keseluruhan, pekerjaan ini melengkapi pengumuman "2 filter yang memberikan 25 hasil".

Screenshot pembaca layar MacOS yang mengumumkan hasil.

Sekarang, pengalaman teknologi pendukung kami yang sangat baik akan diberikan kepada semua pengguna, apa pun cara mereka berinteraksi dengannya.

Kesimpulan

Setelah Anda tahu cara saya melakukannya, bagaimana Anda melakukannya‽ 🙂

Mari kita diversifikasi pendekatan dan pelajari semua cara untuk mem-build di web. Buat demo, tweet link-nya, dan saya akan menambahkannya ke bagian remix komunitas di bawah.

Remix komunitas

Belum ada apa-apa di sini.