mask × data属性 × currentColor で作る、拡張性の高いCSSアイコン設計

CSSでアイコンを実装するとき、こんなことありませんか?

  • アイコンの色を hover ごとに指定している
  • テキストとアイコンの色がズレる
  • SVGの色変更が地味につらい
  • アイコンが増えるたびにCSSが肥大化する

私はずっと「まあ、こんなもんか…」と思っていました。
が、currentColor を発見して、実装がスッキリしました。

今回やりたかったこと

今回やりたかったのは、こんな仕様です。

  • テキストの前にアイコンを表示したい
  • アイコンはSVG(単色)
  • アイコンの色はテキスト色と連動させたい
  • data属性でアイコン種別を切り替えたい
  • 将来WordPress化しても壊れない設計にしたい

完成形のコード(先に見せる)

[data-icon] {
  display: inline-flex;
  align-items: center;
  gap: 18px;

  &::before {
    content: '';
    display: block;
    width: 26px;
    aspect-ratio: 1 / 1;
    background: currentColor;

    -webkit-mask: center / contain no-repeat;
            mask: center / contain no-repeat;
  }
}

$icons: download, mail, seminar, document, blank;

@each $icon in $icons {
  [data-icon="#{$icon}"]::before {
    -webkit-mask-image: url("../img/common/icon_#{$icon}.svg");
            mask-image: url("../img/common/icon_#{$icon}.svg");
  }
}

[data-icon="blank"]::before {
  display: none;
}
<a data-icon="download">資料ダウンロード</a>
<a data-icon="mail">お問い合わせ</a>
など

currentColor とは何か

currentColor は、その要素の color の値を参照するCSSキーワードです。

つまり、

  • テキストの色
  • 枠線の色
  • 背景色
  • maskで使う塗り色

1つの color 指定でまとめて制御できます。

mask × currentColor が強い理由

SVGを mask として使うと、SVGは「形」だけを担当します。
実際の色は background で付けるため、currentColor がそのまま使えます。

&::before {
  background: currentColor;
  mask: center / contain no-repeat;
}

得られるメリット

  • hover時に color を変えるだけ
  • ダークモードも color 切り替えだけ
  • デザイン変更が一瞬

data-icon × SCSSループの良さ

アイコンの切り替えは data属性で行います。
CSS側では SCSS の @each を使ってまとめて定義します。

$icons: download, mail, document;

@each $icon in $icons {
  [data-icon="#{$icon}"]::before {
    mask-image: url("../img/common/icon_#{$icon}.svg");
  }
}

なぜ良いか

  • $iconsにアイコン名を追加するだけでいいのが◎
  • JS不要
  • 将来の拡張が楽
  • WordPress + ACFも相性がいい(ACFの select で icon 名を管理する、とか)

まとめ

  • currentColor は「今の文字色」
  • mask と組み合わせるとアイコン実装が一気に楽になる
  • data-icon × SCSSループで拡張性も高い
  • CSS設計をちゃんとすると、将来の自分が助かる