Memilih Teknik Ikon
Penyimpanan dan rendering ikon adalah pilihan yang terpisah. Misalnya, sprite SVG dapat menyediakan ikon ke sistem ikon CSS atau direferensikan dengan <use>.
- Sistem ikon CSS: Gunakan saat CSS harus memilih kapan dan ikon mana yang akan ditampilkan. Gunakan mask untuk ikon satu warna atau gambar latar untuk ikon berwarna penuh.
- SVG dengan
<use>: Gunakan untuk menggunakan kembali simbol jika opsi gaya bawaannya sudah cukup. CSS tidak dapat menata gaya atau menganimasikan konten SVG yang direferensikan secara arbitrer, tetapi properti yang diwarisi dan properti kustom dapat diteruskan saat simbol dirancang untuk menggunakannya. Misalnya, jalur dapat menggunakanfill="var(--icon-accent, currentColor)"untuk memungkinkan CSS mewarnai bagian ikon tersebut. - SVG inline: Gunakan saat Anda memerlukan gaya visual atau animasi arbitrer dari konten SVG.
<img>dengan filter CSS: Hindari cara ini untuk mewarnai ulang ikon. Filter tidak dapat menerapkan nilai warna CSS arbitrer ke gambar dengan andal, sehingga pewarnaan ulang menjadi rumit dan tidak fleksibel.
Untuk memutuskan, ajukan dua pertanyaan. Apakah CSS perlu memilih atau menukar ikon (default komponen, perubahan status, tema)? Jika ya, gunakan sistem ikon CSS. Apakah Anda perlu menjangkau bagian dalam ikon selain warna? Jika ya dan Anda mengontrol sumber ikon, desain simbol dengan properti kustom dan gunakan <use>; jika Anda tidak mengontrol sumber, sisipkan SVG. Set ikon pihak ketiga jarang mengekspos properti kustom, sehingga <use> terhadapnya hanya memberi Anda warna dan sedikit hal lainnya.
Ikon tersemat atau jarak jauh
Sistem ikon CSS dan <use> dapat mengambil ikon dari URI data, file individual, atau sprite. Penyematan (URI data atau SVG inline) menghindari permintaan dan merender dengan first paint, tetapi memperbesar CSS atau HTML dan tidak dapat di-cache secara terpisah, sehingga cocok untuk sekumpulan kecil ikon yang muncul di setiap halaman. Setiap file di-cache secara independen dan dimuat sesuai permintaan, dengan biaya satu permintaan per file. Sprite adalah satu permintaan yang dapat di-cache untuk seluruh set, tetapi mendownload setiap ikon, baik digunakan maupun tidak, dan <use> hanya dapat mereferensikan sprite dari origin yang sama. Sebaiknya gunakan penyematan untuk beberapa ikon inti dan sprite atau file individual untuk set yang lebih besar.
Jika halaman merender ribuan ikon sekaligus, lakukan tolok ukur pada halaman dan aset yang sebenarnya. Dalam uji stres, SVG inline memiliki sedikit keunggulan performa pada skala tersebut, tetapi hasilnya bergantung pada browser dan implementasi. Uji ketahanan ikon SVG Cloud Four
Pendekatan Utama: Mesin Ikon Berbasis CSS
Ikon adalah token desain: setiap ikon adalah properti kustom yang menyimpan gambar, dan elemen apa pun dapat menampilkan ikon dengan menetapkan --icon-start atau --icon-end. Mesin ini menggabungkan tiga kemampuan CSS:
- Properti kustom terdaftar (
@property) mencegah--icon-startdan--icon-enddiwariskan, sehingga ikon yang ditetapkan pada komponen tidak akan diterapkan ke turunannya. - Kueri gaya penampung (
@container style(...)) mendeteksi saat elemen memiliki ikon yang ditetapkan dan merendernya, tanpa perubahan class atau markup. - Masker CSS dan
currentColormerender ikon dalam elemen pseudo yang mengikuti warna teks elemen. Ukuran, warna, spasi, dan offset vertikal dibaca dari properti--icon-style-*yang dapat diwariskan dengan penggantian yang sesuai, sehingga seluruh subhierarki dapat diberi gaya ulang dari ancestor. Awalan--icon-style-mencegah setelan ini bertabrakan dengan token ikon itu sendiri, yang juga berada di bawah--icon-*.
Menentukan token desain ikon
Tentukan ikon SVG dan penggantian --icon-style-* sebagai properti kustom yang dapat digunakan kembali menggunakan URI data SVG inline atau URL relatif root (misalnya, url("/icons/trash.svg")). Saat ikon dimuat dari file SVG eksternal, gunakan URL relatif root atau URI data tersemat. URL relatif yang digunakan melalui properti kustom dapat di-resolve secara relatif terhadap stylesheet tempat ikon digunakan, bukan stylesheet tempat properti kustom ditentukan. Jika stylesheet tersebut berada di direktori yang berbeda, URL mungkin akan me-resolve ke file yang salah.
Untuk menjaga URL tetap relatif terhadap stylesheet yang menentukan token ikon, properti terdaftar dimaksudkan untuk membantu: nilai yang dihitung dari <url> atau <image> terdaftar di-resolve terhadap stylesheet yang mendeklarasikannya. JANGAN mengandalkan pendaftaran token sebagai <image> dengan URL di initial-value. Chrome dan Safari me-resolve URL initial-value terhadap dokumen, sehingga hanya berfungsi di Firefox. Chrome juga me-resolve nilai yang dideklarasikan di :root terhadap dokumen, kecuali jika sintaksisnya dimulai dengan <url>.
Jika URL relatif tidak dapat dihindari, seperti stylesheet di direktori yang berbeda, daftarkan token dengan sintaksis yang diawali dengan <url> dan deklarasikan URL sebenarnya di :root. Ini akan di-resolve terhadap stylesheet yang mendeklarasikan di setiap browser:
@property --icon-caret {
/* The syntax must start with <url>. A syntax that starts with <image> resolves against the document in Chrome. */
syntax: "<url> | <image> | none";
inherits: true;
initial-value: none;
}
:root {
/* Declared here, the URL resolves against this stylesheet */
--icon-caret: url("icons/caret.svg");
}
Setiap ikon kini berada di dua tempat, dan ada lebih banyak boilerplate, sehingga hal ini hanya bermanfaat jika project memiliki stylesheet di direktori yang berbeda dan jumlah ikon yang dapat dikelola. Setelah sekitar selusin ikon, daftarkan token --icon-* (tidak termasuk --icon-style-*) dari skrip dengan CSS.registerProperty, menggunakan sintaksis yang sama, daripada menulis setiap @property secara manual. Properti terdaftar bersifat global dan tidak berfungsi di shadow root, sehingga mungkin tidak cocok untuk setiap project. Jika URL root-relative atau URI data adalah opsi, URL atau URI tersebut lebih sederhana dan berfungsi di mana saja.
:root {
/* Optional: override the built-in fallbacks (1em, currentColor, 0.3em, -0.02em) */
--icon-style-size: 1em;
--icon-style-spacing: 0.4em;
--icon-trash: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Cpath d='M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0-1 14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2L4 6h16Z'/%3E%3C/svg%3E");
--icon-favorite: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z'/%3E%3C/svg%3E");
}
Merender ikon dari token
Elemen menampilkan ikon dengan menetapkan --icon-start (leading) atau --icon-end (trailing) ke salah satu token di atas. Dua bagian CSS mengubah penetapan tersebut menjadi ikon yang dirender.
Pertama, daftarkan --icon-start dan --icon-end dengan @property. Sintaksis "*" menerima nilai apa pun, dan inherits: false adalah bagian yang penting: tanpanya, ikon yang ditetapkan pada tombol akan diwariskan ke setiap turunan, dan masing-masing turunan juga akan merender ikon tersebut.
@property --icon-start {
syntax: "*";
inherits: false;
}
@property --icon-end {
syntax: "*";
inherits: false;
}
Kedua, kueri gaya penampung mendeteksi saat properti ditetapkan pada elemen dan merender ikon dalam elemen semu. Karena elemen semu tidak mewarisi --icon-start (didaftarkan sebagai tidak diwariskan), deklarasi pertama meneruskan nilai secara eksplisit dengan inherit. Deklarasi yang tersisa menangani ukuran, masking, perataan, dan spasi. Karena pengisian ditetapkan secara default ke currentColor, ikon akan mengadopsi warna teks elemen dan mengikuti transisinya.
/* Style query for elements configuring a start icon */
@container style(--icon-start) {
::before {
--icon-start: inherit; /* Forward the value inside the query block */
content: "" / var(--icon-style-alt, "");
display: inline-block;
width: var(--icon-style-size, 1em);
height: var(--icon-style-size, 1em);
background-color: var(--icon-style-color, currentColor);
mask: var(--icon-start) no-repeat center / contain;
/* Center on the cap height, nudged slightly up for optical balance */
vertical-align: calc(0.5cap - var(--icon-style-size, 1em) / 2 + var(--icon-style-voffset, -0.02em));
flex: none;
pointer-events: none;
forced-color-adjust: none;
}
/* Automatically add spacing ONLY if the element has other text or sibling content */
:where(:not(.icon, :empty))::before {
margin-inline-end: var(--icon-style-spacing, 0.3em);
}
}
Setiap deklarasi memiliki tempatnya sendiri:
vertical-align:middlesejajar dengan setengah tinggi x, yang terlihat rendah untuk sebagian besar set ikon. Pemusatan pada0.5capmembuat bagian tengah ikon tetap saat--icon-style-sizebertambah, dan--icon-style-voffsetnegatif kecil mengimbangi mata yang lebih memperhatikan ruang di atas ikon daripada ruang di bawahnya.flex: none: Elemen semu kosong memiliki ukuran minimum nol, sehingga di dalam tombol flex, elemen ini adalah hal pertama yang menyusut saat labelnya panjang, bahkan berpotensi menjadi tidak ada. Ukuran ikon adalah keputusan desain, bukan sesuatu yang harus dinegosiasikan oleh tekanan tata letak.pointer-events: none: Mencegah ikon menjebak klik atau menjadievent.target.forced-color-adjust: none: Dalam mode warna paksa, browser akan menggantibackground-color, sehingga ikon yang disamarkan akan hilang. Isian mask dipertahankan, dancurrentColormasih mengikuti warna teks yang dipaksakan.content: "" / var(--icon-style-alt, ""): Teks alternatif untuk konten yang dihasilkan memungkinkan kontrol khusus ikon mengambil label aksesibilitasnya dari ikon melalui--icon-style-alt(lihat Kontrol Khusus Ikon di bawah). Teks alternatif kosong default membuat ikon dekoratif tetap tersembunyi dari teknologi pendukung.
Untuk ikon di akhir, ikuti pola yang sama dengan --icon-end dan ::after, bukan --icon-start dan ::before. Gunakan mask: var(--icon-end) dan terapkan spasi dengan margin-inline-start, bukan margin-inline-end.
Elemen mandiri dan khusus ikon
Untuk ikon mandiri atau tombol khusus ikon, terapkan .icon langsung ke elemen kosong (tombol itu sendiri untuk kontrol khusus ikon) dan tetapkan ikonnya dengan --icon. Di sini, --icon adalah properti tingkat lebih tinggi; aturan .icon:empty memetakannya ke --icon-start untuk ikon utama:
.icon:empty {
--icon-start: var(--icon);
}
Contoh Penggunaan
Default komponen di stylesheet
Manfaat utama ikon sebagai properti kustom adalah ikon tersebut berpartisipasi dalam cascade. Tetapkan ikon pada pemilih dan setiap elemen yang cocok akan mendapatkannya, tanpa perubahan markup:
.button-delete {
--icon-start: var(--icon-trash);
}
a[href^="http"]:not([href*="example.com"]) {
--icon-end: var(--icon-external);
}
<button class="button-delete">Delete item</button>
Mengubah ikon dengan status
Karena ikon hanyalah properti, perubahan status adalah penggantian CSS biasa. Tidak ada elemen kedua yang akan ditampilkan atau disembunyikan dan tidak ada class yang akan diaktifkan/dinonaktifkan:
.button-expand {
--icon-start: var(--icon-caret-right);
}
.button-expand[aria-expanded="true"] {
--icon-start: var(--icon-caret-down);
}
Hal yang sama berlaku untuk presentasi: tetapkan --icon-style-size, --icon-style-color, atau --icon-style-spacing pada penampung seperti toolbar dan setiap ikon di dalamnya akan diperbarui.
Ikon sekali pakai dengan gaya inline
Elemen yang tidak tercakup dalam aturan komponen dapat menetapkan properti yang sama secara inline, misalnya <button style="--icon-start: var(--icon-trash)">. Perlakukan ini sebagai jalan keluar, bukan default: setelah ikon yang sama muncul pada jenis elemen yang sama lebih dari sekali, pindahkan ke aturan stylesheet.
Kontrol Khusus Ikon
Tombol khusus ikon menggunakan class .icon dan --icon yang dijelaskan di atas. Beri tombol label aksesibilitas dengan aria-label, atau biarkan ikon menyediakan label melalui --icon-style-alt; tanpa keduanya, ikon yang dihasilkan CSS bersifat dekoratif. Untuk ikon informatif mandiri, ekspos elemen .icon dengan role="img" dan aria-label:
.button-close {
--icon: var(--icon-close);
--icon-style-alt: "Close";
}
<!-- Icon-only interactive control, icon and name from the stylesheet -->
<button class="icon button-close"></button>
<!-- Icon-only interactive control, named via aria-label -->
<button class="icon" style="--icon: var(--icon-trash);" aria-label="Delete item"></button>
<!-- Standalone informative icon -->
<span class="icon" style="--icon: var(--icon-favorite);" role="img" aria-label="Favorite"></span>
Bekerja dengan SVG secara langsung
Jika sistem ikon CSS tidak sesuai, gunakan SVG inline dan <use> bersama-sama, bukan sebagai alternatif. Sisipkan SVG saat ikon pertama kali muncul di halaman dan referensikan dengan <use href="#id"> untuk setiap pengulangan; referensi lokal tidak memerlukan permintaan dan tidak memiliki batasan lintas origin. Cadangkan sprite eksternal (<use href="/icons.svg#id">) untuk ikon yang dibagikan di banyak halaman.
Dengan <use>, konten yang direferensikan akan di-clone ke dalam shadow tree, sehingga CSS halaman tidak dapat memilih elemennya. Yang menjangkaunya adalah pewarisan: color, currentColor, dan properti kustom apa pun. Selain atribut presentasi SVG, CSS modern juga dapat mengubah banyak atribut geometri, sehingga ikon yang Anda kontrol dapat mengekspos hook yang Anda perlukan, misalnya stroke-width="var(--icon-stroke, 2)" atau bahkan d: var(--icon-path, path(…)) pada jalur tertentu.
CSS d tidak didukung di Safari, jadi gunakan penyesuaian ini hanya jika kesenjangan dukungan tersebut dapat diterima. Jika Anda tidak mengontrol ikon, buat ikon tersebut inline.
<use> hal yang perlu diketahui:
viewBoxharus berada di<symbol>atau<svg>yang direferensikan;<svg>luar yang menggabungkan<use>tidak mengambilnya dari referensi, jadi tetapkanwidthdanheightpada wrapper dan pertahankanviewBoxdengan simbol.- Gaya tidak melewati batas di kedua arah:
<style>di dalam sprite eksternal tidak berlaku untuk halaman, dan stylesheet halaman tidak berlaku di dalam hierarki yang direferensikan. Hanya properti yang diwarisi dan properti kustom yang diteruskan. - Referensi eksternal hanya memiliki origin yang sama; sprite lintas origin akan gagal tanpa ada pesan error.
- Sprite yang disisipkan ke dalam dokumen berbagi namespace
idhalaman, jadi gunakan ID simbol namespace untuk menghindari konflik.
Tandai SVG dekoratif dengan aria-hidden="true" dan beri kontrol yang memuatnya label aksesibilitasnya sendiri, seperti pada sistem ikon CSS.
Strategi Penggantian
Properti Kustom Terdaftar
Tanpa @property, --icon-start dan --icon-end berperilaku sebagai properti kustom turunan biasa, sehingga ikon yang ditetapkan pada elemen juga akan dirender pada turunannya. Cegah hal ini dengan mereset properti pada turunan di dalam kueri gaya (@container style(--icon-start) { * { --icon-start: initial; } }), pendekatan yang sama dengan yang digunakan solusi Safari di bawah.
Kueri Gaya Penampung
Untuk mendukung browser tanpa Kueri Gaya Penampung, gunakan class atau pemilih komponen sebagai hook pengganti. Pertahankan pemilihan ikon di properti kustom; pemilih gaya inline saja akan melewatkan ikon yang ditetapkan melalui aturan CSS.
:where(.icon-start, .icon:empty)::before {
content: image-set(linear-gradient(transparent 0 100%), var(--icon-start)) / var(--icon-style-alt, "");
/* icon styles */
}
:where(.icon-start)::before {
--icon-start: inherit;
}
:where(.icon:empty)::before {
--icon-start: var(--icon);
}
:where(.icon-start:not(.icon, :empty))::before {
margin-inline-end: var(--icon-style-spacing, 0.3em);
}
Untuk ikon di akhir, gunakan penggantian class-hook yang sama dengan .icon-end, --icon-end, dan ::after, bukan .icon-start, --icon-start, dan ::before. Terapkan spasi ke konten yang tidak kosong dengan margin-inline-start, bukan margin-inline-end.
Pola image-set() ini membuat content tidak valid saat properti kustom tidak ditetapkan, sehingga kembali ke none dan elemen semu menghilang tanpa menambahkan atau menghapus class. Hook ini tidak menghapus gaya lain elemen semu atau mencegah konflik dengan aturan ::before atau ::after lainnya, jadi gunakan hook ini hanya jika elemen semu tersebut tersedia untuk ikon.
Solusi Browser Safari
Safari memiliki dua bug rendering yang memengaruhi ikon: WebKit #301609 dapat mencegah ikon muncul hingga interaksi pengguna, dan #320220 juga memengaruhi rendering ikon. Solusi di bawah ini mengatasi keduanya:
@keyframes webkit-301609 {}
@supports ((-webkit-nbsp-mode: normal) or (-webkit-touch-callout: none) or (font: -apple-system-body)) and (content-visibility: auto) {
@layer webkit-301609-fix {
::before, ::after {
animation: webkit-301609 0s;
}
}
}
Kueri @supports hanya menargetkan Safari, dan @layer mengurangi konflik animasi.
Bug WebKit #325900 dapat mencegah penggantian langsung --icon-start diterapkan dalam beberapa kasus, seperti mengubah ikon saat kursor diarahkan ke ikon. Hal ini memengaruhi solusi untuk properti terdaftar yang tidak diwariskan. Untuk Safari, daftarkan --icon-start sebagai mewarisi dan reset pada turunan di dalam kueri gaya:
@supports ((-webkit-nbsp-mode: normal) or
(-webkit-touch-callout: none) or
(font: -apple-system-body)) and (content-visibility: auto) {
/* Override previous declaration */
@property --icon-start {
syntax: "*";
inherits: true;
}
@layer webkit-325900-fix {
@container style(--icon-start) {
* {
--icon-start: initial;
}
}
}
}
Reset ini mencegah nilai ikon diterapkan ke turunan. Dengan menyimpannya dalam lapisan, deklarasi ikon tanpa lapisan akan lebih diprioritaskan daripada reset.