アイコン手法の選択
アイコンの保存とレンダリングは別々に選択します。たとえば、SVG スプライトは CSS アイコン システムにアイコンを提供したり、<use> で参照したりできます。
- CSS アイコン システム: CSS でアイコンを表示するタイミングとアイコンを選択する必要がある場合に使用します。単色アイコンにはマスクを使用し、フルカラー アイコンには背景画像を使用します。
<use>を含む SVG: 組み込みのスタイル設定オプションで十分な場合に、シンボルを再利用するために使用します。CSS では、参照される SVG のコンテンツを任意にスタイル設定したりアニメーション化したりすることはできませんが、シンボルが継承されたプロパティとカスタム プロパティを使用するように設計されている場合は、それらを渡すことができます。たとえば、パスでfill="var(--icon-accent, currentColor)"を使用して、CSS でアイコンのその部分に色を付けることができます。- インライン SVG: SVG のコンテンツに任意のスタイル設定やアニメーションが必要な場合に使用します。
<img>と CSS フィルタ: アイコンの色を変更する場合は、この方法を避けてください。フィルタでは、任意の CSS カラー値を画像に確実に適用できないため、色の変更は面倒で柔軟性に欠けます。
判断するには、次の 2 つの質問をします。CSS でアイコンを選択または入れ替える必要があるか(コンポーネントのデフォルト、状態の変化、テーマ設定)?「はい」の場合は、CSS アイコン システムを使用します。色だけでなく、アイコンの内部にまで手を加える必要があるでしょうか。「はい」で、アイコンのソースを制御できる場合は、カスタム プロパティを使用してシンボルを設計し、<use> を使用します。ソースを制御できない場合は、SVG をインライン化します。サードパーティのアイコンセットでカスタム プロパティが公開されることはほとんどないため、<use> を使用しても色以外はほとんど取得できません。
埋め込みアイコンまたはリモート アイコン
CSS アイコン システムと <use> の両方で、データ URI、個々のファイル、スプライトからアイコンを取得できます。埋め込み(データ URI またはインライン SVG)は、リクエストを回避し、最初のペイントでレンダリングしますが、CSS または HTML が肥大化し、個別にキャッシュに保存できないため、すべてのページに表示される小さなアイコンセットに適しています。個々のファイルは個別にキャッシュされ、オンデマンドで読み込まれます。この場合、1 つのリクエストにつき 1 つのコストがかかります。スプライトは、セット全体に対してキャッシュ可能な 1 つのリクエストですが、使用されているかどうかにかかわらず、すべてのアイコンをダウンロードします。また、<use> は同じオリジンのスプライトのみを参照できます。少数のコアアイコンには埋め込みを使用し、より大きなセットにはスプライトまたは個別のファイルを使用することをおすすめします。
ページで一度に数千個のアイコンをレンダリングする場合は、実際のページとアセットをベンチマークします。ストレステストでは、インライン SVG のパフォーマンスがその規模でわずかに優位でしたが、結果はブラウザと実装によって異なります。Cloud Four の SVG アイコンのストレステスト
主なアプローチ: CSS 駆動型アイコン エンジン
アイコンはデザイン トークンです。各アイコンは画像を保持するカスタム プロパティであり、--icon-start または --icon-end を設定することで、任意の要素でアイコンを表示できます。このエンジンは、次の 3 つの CSS 機能を組み合わせています。
- 登録済みのカスタム プロパティ(
@property)は、--icon-startと--icon-endの継承を防止するため、コンポーネントに設定されたアイコンは子にカスケードされません。 - コンテナ スタイルクエリ(
@container style(...))は、要素にアイコンが割り当てられていることを検出し、クラスやマークアップを変更せずにレンダリングします。 - CSS マスクと
currentColorは、要素のテキストの色に従う疑似要素でアイコンをレンダリングします。サイズ、色、間隔、垂直オフセットは、適切なフォールバックを備えた継承可能な--icon-style-*プロパティから読み取られるため、サブツリー全体を祖先から再スタイル設定できます。--icon-style-接頭辞は、これらの設定がアイコン トークン自体と競合しないようにします。アイコン トークンも--icon-*にあります。
アイコン デザイン トークンの定義
SVG アイコンと --icon-style-* オーバーライドを、インライン SVG データ URI またはルート相対 URL(例: url("/icons/trash.svg"))を使用して、再利用可能なカスタム プロパティとして定義します。アイコンが外部 SVG ファイルから読み込まれる場合は、ルート相対 URL または埋め込みデータ URI を使用します。カスタム プロパティで使用される相対 URL は、カスタム プロパティが定義されているスタイルシートではなく、アイコンが使用されているスタイルシートを基準として解決されることがあります。これらのスタイルシートが異なるディレクトリにある場合、URL が間違ったファイルに解決される可能性があります。
アイコン トークンを定義するスタイルシートを基準とした URL を維持するために、登録済みプロパティが役立ちます。登録済みの <url> または <image> の計算値は、それを宣言したスタイルシートを基準として解決されます。initial-value の URL を使用してトークンを <image> として登録することに依存しないでください。Chrome と Safari はドキュメントに対して initial-value URL を解決するため、Firefox でのみ機能します。また、構文が <url> で始まらない限り、:root で宣言された値はドキュメントに対して解決されます。
異なるディレクトリにあるスタイルシートなど、相対 URL が避けられない場合は、<url> で始まる構文でトークンを登録し、:root で実際の URL を宣言します。これは、すべてのブラウザで宣言元のスタイルシートに対して解決されます。
@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");
}
各アイコンが 2 か所に配置され、ボイラープレートが大幅に増えるため、この方法は、プロジェクトに異なるディレクトリのスタイルシートがあり、アイコンの数が管理可能な場合にのみ有効です。アイコンが 12 個以上ある場合は、各 @property を手動で記述するのではなく、同じ構文を使用して、CSS.registerProperty を含むスクリプトから --icon-* トークン(--icon-style-* を除く)を登録します。登録されたプロパティはグローバルであり、シャドウルートでは機能しないため、すべてのプロジェクトに適合するとは限りません。ルート相対 URL またはデータ URI を使用できる場合は、それらの方がシンプルで、どこでも機能します。
: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");
}
トークンからアイコンをレンダリングする
要素は、--icon-start(先頭)または --icon-end(末尾)を上記のトークンのいずれかに設定することで、アイコンを表示します。2 つの CSS が、その割り当てをレンダリングされたアイコンに変換します。
まず、--icon-start と --icon-end を @property に登録します。"*" 構文は任意の値を受け入れます。inherits: false は重要な部分です。これがないと、ボタンに設定されたアイコンがすべての子孫に継承され、それぞれがアイコンをレンダリングすることになります。
@property --icon-start {
syntax: "*";
inherits: false;
}
@property --icon-end {
syntax: "*";
inherits: false;
}
次に、コンテナ スタイルクエリが、要素にプロパティが設定されたことを検出し、疑似要素にアイコンをレンダリングします。疑似要素は --icon-start を継承しないため(非継承として登録されているため)、最初の宣言では inherit を使用して値を明示的に転送します。残りの宣言は、サイズ設定、マスキング、配置、間隔を処理します。塗りつぶしはデフォルトで currentColor に設定されているため、アイコンは要素のテキストの色を採用し、その遷移に従います。
/* 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);
}
}
各宣言には、次のような役割があります。
vertical-align:middleは x ハイトの半分に揃えられます。これは、ほとんどのアイコンセットで目に見えて低くなります。0.5capを中心に配置すると、--icon-style-sizeが大きくなってもアイコンの中心は固定されます。また、--icon-style-voffsetの小さな負の値は、アイコンの下のスペースよりも上のスペースを重視する目を補正します。flex: none: 空の疑似要素の最小サイズはゼロであるため、flex ボタン内では、ラベルが長い場合に最初に縮小され、場合によっては何も表示されなくなります。アイコンのサイズはデザイン上の決定事項であり、レイアウトの制約によって調整されるものではありません。pointer-events: none: アイコンがクリックをトラップしたり、event.targetになったりしないようにします。forced-color-adjust: none: 強制カラーモードでは、ブラウザがbackground-colorを置き換えるため、マスクされたアイコンが表示されなくなります。マスクの塗りつぶしは保持され、currentColorは強制されたテキストの色に従います。content: "" / var(--icon-style-alt, ""): 生成されたコンテンツの代替テキスト。アイコンのみのコントロールが、--icon-style-altを介してアイコンからユーザー補助機能用の名前を取得できるようにします(下記のアイコンのみのコントロールを参照)。デフォルトの空の alt 属性により、装飾アイコンは支援技術から隠されます。
末尾のアイコンの場合は、--icon-start と ::before の代わりに --icon-end と ::after を使用して同じパターンに従います。mask: var(--icon-end) を使用し、margin-inline-end ではなく margin-inline-start でスペースを適用します。
スタンドアロン要素とアイコンのみの要素
スタンドアロン アイコンまたはアイコンのみのボタンの場合は、空の要素(アイコンのみのコントロールの場合はボタン自体)に .icon を直接適用し、--icon でアイコンを設定します。ここで、--icon は上位レベルのプロパティです。.icon:empty ルールは、先頭アイコンの --icon-start にマッピングします。
.icon:empty {
--icon-start: var(--icon);
}
使用例
スタイルシートのコンポーネントのデフォルト
カスタム プロパティとしてアイコンを使用する主なメリットは、カスケードに参加できることです。セレクタにアイコンを設定すると、一致するすべての要素にアイコンが適用されます。マークアップを変更する必要はありません。
.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>
状態に応じてアイコンを変更する
アイコンは単なるプロパティであるため、状態の変更は通常の CSS オーバーライドです。表示または非表示にする 2 つ目の要素はなく、切り替えるクラスもありません。
.button-expand {
--icon-start: var(--icon-caret-right);
}
.button-expand[aria-expanded="true"] {
--icon-start: var(--icon-caret-down);
}
これは表示にも当てはまります。ツールバーなどのコンテナに --icon-style-size、--icon-style-color、--icon-style-spacing を設定すると、その中のすべてのアイコンが更新されます。
インライン スタイルを使用した 1 回限りのアイコン
コンポーネント ルールが適用されない要素は、同じプロパティをインラインで設定できます(例: <button style="--icon-start: var(--icon-trash)">)。これはデフォルトではなく、エスケープ ハッチとして扱ってください。同じアイコンが同じ種類の要素に複数回表示される場合は、スタイルシート ルールに移動します。
アイコンのみのコントロール
アイコンのみのボタンでは、前述の .icon クラスと --icon を使用します。aria-label を使用してボタンにユーザー補助機能用の名前を付けるか、--icon-style-alt を使用してアイコンに名前を付けます。どちらも指定しない場合、CSS で生成されたアイコンは装飾的なものになります。スタンドアロンの情報アイコンの場合は、role="img" と aria-label を使用して .icon 要素を公開します。
.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>
SVG を直接操作する
CSS アイコン システムが適切でない場合は、インライン SVG と <use> を代替としてではなく、組み合わせて使用します。ページにアイコンが初めて表示されるときに SVG をインライン化し、繰り返すたびに <use href="#id"> で参照します。ローカル参照ではリクエストが発生せず、クロスオリジンの制限もありません。多くのページで共有されるアイコン用に外部スプライト(<use href="/icons.svg#id">)を予約します。
<use> を使用すると、参照されたコンテンツがシャドウツリーにクローンされるため、ページの CSS でその要素を選択できません。到達するのは、color、currentColor、カスタム プロパティなどの継承です。最新の CSS では、SVG プレゼンテーション属性だけでなく、多くのジオメトリ属性も変更できるため、制御するアイコンで、必要なフック(特定のパスの stroke-width="var(--icon-stroke, 2)" や d: var(--icon-path, path(…)) など)を正確に公開できます。
CSS d は Safari でサポートされていないため、このカスタマイズは、そのサポートギャップが許容される場合にのみ使用してください。アイコンを制御しない場合は、インライン化します。
知っておくべき <use> の注意点:
viewBoxは<symbol>または参照される<svg>に配置する必要があります。<use>をラップする外側の<svg>は参照から を取得しないため、ラッパーにwidthとheightを設定し、viewBoxをシンボルとともに保持します。- スタイルはどちらの方向にも境界を越えません。外部スプライト内の
<style>はページに適用されず、ページ スタイルシートは参照されたツリー内に適用されません。継承されたプロパティとカスタム プロパティのみが渡されます。 - 外部参照は同一オリジンのみです。クロスオリジンのスプライトは、エラーを発生させずに失敗します。
- ドキュメントにインライン化されたスプライトは、ページの
id名前空間を共有するため、名前空間のシンボル ID を使用して競合を回避します。
装飾的な SVG に aria-hidden="true" をマークし、CSS アイコンシステムと同様に、それを含むコントロールに独自のユーザー補助機能用の名前を付けます。
代替戦略
登録済みのカスタム プロパティ
@property がないと、--icon-start と --icon-end は通常の継承カスタム プロパティとして動作するため、要素に設定されたアイコンは子孫にもレンダリングされます。これを防ぐには、スタイルクエリ(@container style(--icon-start) { * { --icon-start: initial; } })内の子のプロパティをリセットします。これは、以下の Safari の回避策で使用されているのと同じ方法です。
コンテナ スタイルクエリ
コンテナ スタイルクエリに対応していないブラウザをサポートするには、クラスまたはコンポーネント セレクタをフォールバック フックとして使用します。カスタム プロパティでアイコンの選択を維持します。インライン スタイル セレクタだけでは、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);
}
末尾のアイコンの場合は、.icon-start、--icon-start、::before の代わりに .icon-end、--icon-end、::after を使用して、同じクラスフック フォールバックを使用します。margin-inline-end ではなく margin-inline-start を使用して、空でないコンテンツにスペースを適用します。
この image-set() パターンでは、カスタム プロパティが設定されていない場合に content が無効になるため、none にフォールバックし、クラスを追加または削除することなく疑似要素が消えます。疑似要素の他のスタイルを削除したり、他の ::before ルールや ::after ルールとの競合を防いだりすることはありません。そのため、これらのフックは、アイコンに疑似要素を使用できる場合にのみ使用してください。
Safari ブラウザの回避策
Safari には、アイコンに影響する 2 つのレンダリング バグがあります。WebKit #301609 では、ユーザー操作が行われるまでアイコンが表示されないことがあり、#320220 もアイコンのレンダリングに影響します。以下の回避策は、両方の問題に対応しています。
@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;
}
}
}
@supports クエリは Safari のみをターゲットとし、@layer はアニメーションの競合を軽減します。
WebKit のバグ #325900 により、ホバー時のアイコンの変更など、場合によっては --icon-start の直接オーバーライドが有効にならないことがあります。これは、継承されない登録済みプロパティの回避策に影響します。Safari の場合は、--icon-start を継承として登録し、スタイルクエリ内の子孫でリセットします。
@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;
}
}
}
}
リセットすると、アイコン値が子孫に伝播されなくなります。レイヤに保持することで、レイヤ化されていないアイコン宣言がリセットよりも優先されます。