Ringkasan dasar tentang cara membuat komponen tombol terpisah yang dapat diakses.
Dalam postingan ini saya ingin berbagi pemikiran tentang cara membuat tombol terpisah . Coba demo.
Jika Anda lebih suka menonton video, berikut versi YouTube untuk postingan ini:
Ringkasan
Tombol terpisah adalah tombol yang menyembunyikan tombol utama dan daftar tombol tambahan. {i>Tool<i} berguna untuk mengekspos tindakan umum sambil menyarangkan data sekunder, yang lebih jarang digunakan tindakan hingga diperlukan. Tombol terpisah bisa sangat penting untuk membantu desain yang sibuk terasa minimal. Tombol pemisahan lanjutan bahkan dapat mengingat tindakan terakhir pengguna dan mempromosikannya pada posisi utama.
Tombol pemisahan umum dapat ditemukan di aplikasi email Anda. Tindakan utama dikirim, namun mungkin Anda dapat mengirimnya nanti atau menyimpan draf sebagai gantinya:
Area tindakan bersama terlihat bagus, karena pengguna tidak perlu melihat-lihat. Mereka mengetahui bahwa tindakan email penting terdapat di tombol pemisahan.
Suku cadang
Mari kita uraikan bagian-bagian penting dari tombol terpisah sebelum membahas orkestrasi dan pengalaman pengguna akhir secara keseluruhan. Aksesibilitas VisBug digunakan di sini untuk membantu menampilkan tampilan makro komponen, sehingga aspek HTML, gaya, dan aksesibilitas untuk setiap bagian utama.
Penampung tombol pemisahan tingkat atas
Komponen level tertinggi adalah flexbox inline, dengan class
gui-split-button
, yang berisi tindakan utama
dan .gui-popup-button
.
Tombol tindakan utama
<button>
yang awalnya terlihat dan dapat difokuskan cocok dalam container dengan
dua bentuk sudut
yang sesuai untuk
fokus,
arahkan kursor dan
interaksi aktif ke
muncul dalam .gui-split-button
.
Tombol pop-up
"Tombol pop-up" {i>support<i} adalah untuk mengaktifkan
dan menunjukkan daftar
tombol sekunder. Perhatikan bahwa ini bukan <button>
dan tidak dapat difokuskan. Namun,
ini merupakan anchor pemosisian untuk .gui-popup
dan host untuk :focus-within
yang digunakan
untuk menampilkan pop-up.
Kartu pop-up
Ini adalah turunan kartu mengambang ke anchor-nya
.gui-popup-button
, diposisikan absolut dan
yang menggabungkan daftar tombol secara semantik.
Tindakan sekunder
<button>
yang dapat difokuskan dengan ukuran font yang sedikit lebih kecil dari warna utama
tombol tindakan dilengkapi ikon dan
gaya ke tombol utama.
Properti kustom
Variabel-variabel berikut membantu dalam menciptakan keselarasan warna dan pusat untuk memodifikasi nilai yang digunakan di seluruh komponen.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --light (prefers-color-scheme: light);
.gui-split-button {
--theme: hsl(220 75% 50%);
--theme-hover: hsl(220 75% 45%);
--theme-active: hsl(220 75% 40%);
--theme-text: hsl(220 75% 25%);
--theme-border: hsl(220 50% 75%);
--ontheme: hsl(220 90% 98%);
--popupbg: hsl(220 0% 100%);
--border: 1px solid var(--theme-border);
--radius: 6px;
--in-speed: 50ms;
--out-speed: 300ms;
@media (--dark) {
--theme: hsl(220 50% 60%);
--theme-hover: hsl(220 50% 65%);
--theme-active: hsl(220 75% 70%);
--theme-text: hsl(220 10% 85%);
--theme-border: hsl(220 20% 70%);
--ontheme: hsl(220 90% 5%);
--popupbg: hsl(220 10% 30%);
}
}
Tata letak dan warna
Markup
Elemen dimulai sebagai <div>
dengan nama class kustom.
<div class="gui-split-button"></div>
Tambahkan tombol utama dan elemen .gui-popup-button
.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"></span>
</div>
Perhatikan atribut aria aria-haspopup
dan aria-expanded
. Isyarat ini adalah
penting bagi pembaca layar untuk mengetahui kemampuan dan status pemisahan
pengalaman tombol. Atribut title
bermanfaat bagi semua orang.
Tambahkan ikon <svg>
dan elemen penampung .gui-popup
.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup"></ul>
</span>
</div>
Untuk penempatan pop-up yang mudah, .gui-popup
adalah turunan dari tombol yang
akan memperluasnya. Satu-satunya yang menarik dengan strategi ini adalah .gui-split-button
container tidak dapat menggunakan overflow: hidden
, karena akan memotong pop-up agar tidak
disajikan secara visual.
<ul>
yang diisi dengan konten <li><button>
akan mengumumkan dirinya sebagai "tombol
"daftar" bagi pembaca layar, tepatnya
antarmuka yang ditampilkan.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup">
<li>
<button>Schedule for later</button>
</li>
<li>
<button>Delete</button>
</li>
<li>
<button>Save draft</button>
</li>
</ul>
</span>
</div>
Untuk gaya dan bersenang-senang dengan warna, saya telah menambahkan ikon ke tombol sekunder dari https://heroicons.com. Ikon bersifat opsional untuk keduanya tombol primer dan sekunder.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup">
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
Schedule for later
</button></li>
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Delete
</button></li>
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
</svg>
Save draft
</button></li>
</ul>
</span>
</div>
Gaya
Dengan HTML dan konten pada tempatnya, gaya siap untuk memberikan warna dan tata letak.
Menata gaya penampung tombol pemisahan
Jenis tampilan inline-flex
berfungsi dengan baik untuk komponen penggabungan ini karena
harus sesuai dengan tombol, tindakan, atau elemen terpisah lainnya.
.gui-split-button {
display: inline-flex;
border-radius: var(--radius);
background: var(--theme);
color: var(--ontheme);
fill: var(--ontheme);
touch-action: manipulation;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
Gaya <button>
Tombol sangat baik dalam menyamarkan berapa banyak kode yang diperlukan. Anda mungkin perlu membatalkan atau mengganti gaya {i>default<i} browser, tetapi Anda juga harus menerapkan beberapa pewarisan, menambahkan status interaksi dan beradaptasi dengan berbagai preferensi pengguna, input. Gaya tombol bertambah dengan cepat.
Tombol-tombol ini berbeda dari tombol biasa karena berbagi latar belakang dengan elemen induk. Biasanya, tombol memiliki warna latar belakang dan teksnya. Namun, mereka membagikannya, dan hanya menerapkan latar belakang mereka sendiri pada interaksi.
.gui-split-button button {
cursor: pointer;
appearance: none;
background: none;
border: none;
display: inline-flex;
align-items: center;
gap: 1ch;
white-space: nowrap;
font-family: inherit;
font-size: inherit;
font-weight: 500;
padding-block: 1.25ch;
padding-inline: 2.5ch;
color: var(--ontheme);
outline-color: var(--theme);
outline-offset: -5px;
}
Menambahkan status interaksi dengan beberapa CSS kelas semu dan penggunaan pencocokan properti khusus untuk status:
.gui-split-button button {
…
&:is(:hover, :focus-visible) {
background: var(--theme-hover);
color: var(--ontheme);
& > svg {
stroke: currentColor;
fill: none;
}
}
&:active {
background: var(--theme-active);
}
}
Tombol utama memerlukan beberapa gaya khusus untuk melengkapi efek desain:
.gui-split-button > button {
border-end-start-radius: var(--radius);
border-start-start-radius: var(--radius);
& > svg {
fill: none;
stroke: var(--ontheme);
}
}
Terakhir, untuk gaya, tombol tema terang dan ikon mendapatkan shadow:
.gui-split-button {
@media (--light) {
& > button,
& button:is(:focus-visible, :hover) {
text-shadow: 0 1px 0 var(--theme-active);
}
& > .gui-popup-button > svg,
& button:is(:focus-visible, :hover) > svg {
filter: drop-shadow(0 1px 0 var(--theme-active));
}
}
}
Tombol yang bagus telah memperhatikan interaksi mikro dan detail kecil.
Catatan tentang :focus-visible
Perhatikan bagaimana gaya tombol menggunakan :focus-visible
, bukan :focus
. :focus
adalah sentuhan penting untuk membuat antarmuka pengguna yang mudah diakses, tetapi ia memiliki satu
{i>downfall<i}: tidak cerdas tentang apakah pengguna perlu melihatnya atau tidak
tidak, itu akan diterapkan untuk fokus apa pun.
Video di bawah ini mencoba menguraikan interaksi mikro ini, untuk menunjukkan bagaimana
:focus-visible
adalah alternatif cerdas.
Menata gaya tombol pop-up
Flexbox 4ch
untuk menempatkan ikon di tengah dan menambatkan daftar tombol pop-up. Suka
tombol utama, akan tetap transparan hingga kursor diarahkan ke atasnya atau berinteraksi dengannya
dan direntangkan hingga penuh.
.gui-popup-button {
inline-size: 4ch;
cursor: pointer;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
border-inline-start: var(--border);
border-start-end-radius: var(--radius);
border-end-end-radius: var(--radius);
}
Lapisan saat pengarahan kursor, fokus, dan status aktif dengan CSS
Nesting dan
Pemilih fungsi :is()
:
.gui-popup-button {
…
&:is(:hover,:focus-within) {
background: var(--theme-hover);
}
/* fixes iOS trying to be helpful */
&:focus {
outline: none;
}
&:active {
background: var(--theme-active);
}
}
Gaya ini adalah hook utama untuk menampilkan dan menyembunyikan pop-up. Jika
.gui-popup-button
memiliki focus
di salah satu turunannya, tetapkan opacity
, posisi
dan pointer-events
, pada ikon dan pop-up.
.gui-popup-button {
…
&:focus-within {
& > svg {
transition-duration: var(--in-speed);
transform: rotateZ(.5turn);
}
& > .gui-popup {
transition-duration: var(--in-speed);
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
}
}
Setelah gaya masuk dan keluar selesai, bagian terakhir adalah kondisional transformasi transisi bergantung pada preferensi gerakan pengguna:
.gui-popup-button {
…
@media (--motionOK) {
& > svg {
transition: transform var(--out-speed) ease;
}
& > .gui-popup {
transform: translateY(5px);
transition:
opacity var(--out-speed) ease,
transform var(--out-speed) ease;
}
}
}
Perhatikan dengan cermat kode tersebut akan melihat bahwa opasitas masih dialihkan untuk pengguna yang lebih suka mengurangi gerakan.
Menata gaya pop-up
Elemen .gui-popup
adalah daftar tombol kartu mengambang menggunakan properti kustom
dan unit relatif menjadi lebih kecil, secara interaktif sesuai dengan
tombol, dan merek dengan penggunaan warna. Perhatikan ikon-ikon tersebut lebih sedikit kontras,
lebih tipis, dan bayangannya memiliki sentuhan biru merek. Seperti pada tombol,
UI dan UX yang kuat adalah hasil dari
menumpuk detail kecil ini.
.gui-popup {
--shadow: 220 70% 15%;
--shadow-strength: 1%;
opacity: 0;
pointer-events: none;
position: absolute;
bottom: 80%;
left: -1.5ch;
list-style-type: none;
background: var(--popupbg);
color: var(--theme-text);
padding-inline: 0;
padding-block: .5ch;
border-radius: var(--radius);
overflow: hidden;
display: flex;
flex-direction: column;
font-size: .9em;
transition: opacity var(--out-speed) ease;
box-shadow:
0 -2px 5px 0 hsl(var(--shadow) / calc(var(--shadow-strength) + 5%)),
0 1px 1px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 10%)),
0 2px 2px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 12%)),
0 5px 5px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 13%)),
0 9px 9px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 14%)),
0 16px 16px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 20%))
;
}
Ikon dan tombol diberi warna merek untuk ditata dengan baik dalam setiap warna gelap dan kartu bertema terang:
.gui-popup {
…
& svg {
fill: var(--popupbg);
stroke: var(--theme);
@media (prefers-color-scheme: dark) {
stroke: var(--theme-border);
}
}
& button {
color: var(--theme-text);
width: 100%;
}
}
Pop-up tema gelap memiliki tambahan teks dan bayangan ikon, plus bayangan kotak yang intens:
.gui-popup {
…
@media (--dark) {
--shadow-strength: 5%;
--shadow: 220 3% 2%;
& button:not(:focus-visible, :hover) {
text-shadow: 0 1px 0 var(--ontheme);
}
& button:not(:focus-visible, :hover) > svg {
filter: drop-shadow(0 1px 0 var(--ontheme));
}
}
}
Gaya ikon <svg>
umum
Semua ikon memiliki ukuran relatif dengan tombol font-size
yang digunakan di dalamnya
menggunakan unit ch
sebagai
inline-size
. Masing-masing juga diberi beberapa gaya untuk
membantu menguraikan ikon secara lembut dan
lancar.
.gui-split-button svg {
inline-size: 2ch;
box-sizing: content-box;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2px;
}
Tata letak kanan-ke-kiri
Properti logis melakukan semua tugas yang kompleks.
Berikut adalah daftar properti logis yang digunakan:
- display: inline-flex
membuat elemen fleksibel inline.
- padding-block
dan padding-inline
sebagai pasangan, bukan padding
secara singkat, dapatkan manfaat
memberi padding pada sisi logis.
- border-end-start-radius
dan
teman akan
sudut membulat berdasarkan arah dokumen.
- inline-size
, bukan width
, memastikan ukuran tidak terikat dengan dimensi fisik.
- border-inline-start
menambahkan batas ke awal, yang mungkin di kanan atau kiri bergantung pada arah skrip.
JavaScript
Hampir semua JavaScript berikut adalah untuk meningkatkan aksesibilitas. Dua dari {i>help library<i} digunakan untuk membuat tugas lebih mudah. BlingBlingJS digunakan untuk ringkasan Kueri DOM dan penyiapan pemroses peristiwa yang mudah, sekaligus roving-ux membantu memfasilitasi aksesibilitas interaksi keyboard dan gamepad untuk pop-up.
import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'
const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')
Dengan library di atas yang telah diimpor serta elemen yang dipilih dan disimpan ke dalam variabel, mengupgrade pengalaman tinggal beberapa fungsi lagi untuk diselesaikan.
Indeks keliling
Saat keyboard atau pembaca layar memfokuskan .gui-popup-button
, kita ingin
meneruskan fokus ke tombol pertama (atau yang terakhir difokuskan) di
.gui-popup
. Library membantu kita melakukan ini dengan element
dan target
parameter.
popupButtons.forEach(element =>
rovingIndex({
element,
target: 'button',
}))
Elemen ini kini meneruskan fokus ke turunan <button>
target dan memungkinkan
navigasi tombol panah standar untuk menjelajahi opsi.
Mengalihkan aria-expanded
Meskipun terlihat jelas bahwa pop-up muncul dan tersembunyi, pembaca layar membutuhkan lebih dari sekadar isyarat visual. JavaScript digunakan di sini untuk melengkapi interaksi :focus-within
berbasis CSS dengan mengalihkan atribut yang sesuai pada pembaca layar.
popupButtons.on('focusin', e => {
e.currentTarget.setAttribute('aria-expanded', true)
})
popupButtons.on('focusout', e => {
e.currentTarget.setAttribute('aria-expanded', false)
})
Mengaktifkan tombol Escape
Fokus pengguna sengaja diarahkan ke jebakan, yang berarti kita perlu
memberi cara untuk pergi. Cara yang paling umum adalah mengizinkan penggunaan tombol Escape
.
Untuk melakukannya, perhatikan penekanan tombol pada tombol {i>pop-up<i}, karena setiap peristiwa {i>keyboard<i} aktif
turunan akan muncul di induk ini.
popupButtons.on('keyup', e => {
if (e.code === 'Escape')
e.target.blur()
})
Jika tombol pop-up melihat penekanan tombol Escape
, tombol ini akan menghapus fokus dari tombol itu sendiri
dengan
blur()
.
Klik tombol pisahkan
Terakhir, jika pengguna mengeklik, mengetuk, atau {i>keyboard<i}
berinteraksi dengan tombol, maka
aplikasi perlu melakukan tindakan yang sesuai. Gelembung acara digunakan
sekali lagi di sini, tetapi kali ini di container .gui-split-button
, untuk menangkap tombol
dari pop-up turunan atau tindakan utama.
splitButtons.on('click', event => {
if (event.target.nodeName !== 'BUTTON') return
console.info(event.target.innerText)
})
Kesimpulan
Sekarang setelah Anda tahu bagaimana saya melakukannya, bagaimana Anda akan 🙂
Mari kita diversifikasi pendekatan kami dan mempelajari semua cara untuk membangun di web. Buat demo, link tweet saya, dan saya akan menambahkannya ke bagian remix komunitas di bawah ini.