こなさんみんばんわ。

あんなことがあった直後だからきっとそのことを書くだろう書くに違いない…と思わせておいて、それはあえて次の機会にして🤣 今日は普通の CSS 記事を出します。そもそもこれ週末に書こうと思ってたネタなんすよ、ええ、すみません。涙

という訳で、前置きはこれくらいにしましてさっそく本編に入ります。

アイコンとテキストの縦位置中央揃えがしたい

テキストと画像の縦位置を CSS で中央揃えにしたい場面というのは割と多いですよね。例えばアイコンの横に短いラベルがあるボタンとか。

<button type="button">
  <img alt="" src="icon.svg" width="16" height="16"> テキストラベル
</button>

でもこの HTML だけだと通常画像の下端はテキストのベースラインに揃うので、ちょっとアイコンの方が上にズレてしまう…というのは、おそらくすべてのフロントエンド技術者が一度は通ってきた道かと思います😅1

そこでこれを両者の中央のラインで揃えたいとなる訳ですが、2025 年現在の CSS でこれを実現するには Flexbox (CSS Flexible Box Layout Module) を使えばおおむね事足ります2

button {
  display: flex;
  align-items: center;
  gap: 5px; /* ← ここは任意でいい感じの値に調整 */
}

これだけです。楽ちんですね。文珍師匠の一番弟子ちがうよ。

実は Flexbox が使えない場面がある

ところが、実は Flexbox を使えない場面というのが存在したりします。そんなんあるんか? と思われるでしょうがあるんですよ、というか実際ありました

例 1. 表のセルの中(など固有の display 値を持つ要素内)

たとえば 8 月末にこのブログに掲載した SVG ファビコンに関する記事があります。この記事のためにひとつ検証用サイトを立ち上げてるんですが、その確認用ページ()では結果の表のセル内に凡例のアイコンとテキストを並べています。そして親の <table> 要素には border-collapse: collapse を適用し、セル間の枠線を折りたたむようにしています。

<!-- 諸々省略 -->
  <td>
    <img alt="" class="favicon" src="/assets/favicon-svg-source.svg" width="24" height="24"> SVG
  </td>
<!-- おなじく -->

さて、この状態で上で書いたようなことを実現するために <td> 要素に display: flex としてしまうとあら大変、セルの枠線がおかしなことになってしまいます

画像:

確認ページの例 を Google Chrome で開き、中央行右列の <td> 要素に display: flex を当ててみると、上下のボーダーが崩れて二重になってしまった(分かりやすくするためにブラウザ側でズームしています)

なぜこういうことが起こるかというと <td> 要素に display: flex を与えた時点で、当たり前ですが display 値が初期値の table-cell じゃなくなってしまうからですね。border-collapse は表関連の display 値を持つ要素以外には効果がないですから、フレックスコンテナとなってしまったこのセルの枠線にはその効果が及ばず、こうやって個別の枠線に分解されてしまうという訳です。

例 2. テキストの一部がマークアップを含む場合

ボタンのラベルだとあまりないですが、普通の短いテキスト内だと十分にありうるパターンですね。<strong> のような要素ではそこまで問題になりませんが、パッと思いつくだけでも

  • <ruby> 関連要素でルビが振られたテキストを含む
  • <small> のように文字サイズが異なる(小さい)テキストを含む
  • <sup>, <sub> など元々アラインメントが異なるテキストを含む

といったものがあります。いずれも単純に display: flex してしまうと意図しない結果を生み出します。

See the Pen ruby, small, sub and sup by Jeffrey Francesco (@jforg) on CodePen.

(あっちなみにこの記事の CodePen デモのアイコン画像はすべて <img> じゃなくてインライン SVG に変更してやってます。Pro じゃなくて画像をアップロードできないので…😭)

例 3. 普通のテキスト段落の中に画像を含める場合

たとえば何らかの画面操作をテキストで説明したい場合に、文章の中に操作子の画像があると、理解しやすくなることがあります。あると思います。ありませんかね?

<p>
  スマートフォンでナビゲーションメニューを開くには、固定ヘッダー(画面の上部)にある
  <img alt="「メニューを開く」ボタン(横向きの三本線が描かれています)" src="menubtn.svg" width="32" height="32">
  をタップします。
</p>

例を挙げるとこんな感じの文章がある時に、途中に挿入されるアイコンサイズの小さな画像をテキストと揃えたい場合です。これはさすがに Flexbox では対応できませんので、何か他の方法で実現する必要があります。


どうでしょう? もちろん 1. や 2. の例については <td> 内の要素群や画像以外の一連のテキストを <span> などでラップしたうえでスタイルを当てれば解決できますが、そう簡単にマークアップを変更できない場合もあるでしょうし、そのためだけに(本来であれば)不要なタグを加えたくないという方もいるでしょう。

そういった場面に対処するためにも Flexbox 以外の方法を知っておいて損はないはずです。

Flexbox を使わずに縦中央揃えを実現する CSS

それでは本題、Flexbox を使わずにアイコンとテキストの縦位置中央揃えを実現してみましょう。今回は話を単純にするために、アイコンの画像サイズは縦横 16px で固定、CSS などで拡大縮小されるようなことはないものとします。普通の画像と区別するために icon というクラス名を <img> 要素に割り当てておきます。

<!-- 冒頭のコードに `icon` クラスを追加しただけ -->
<button type="button">
  <img alt="" class="icon" src="icon.svg" width="16" height="16"> テキストラベル
</button>

もう先に答えを書いてしまいますが、CSS コードは次のとおりです。登場するのは vertical-align プロパティに cap 単位、そして round() 関数です。

.icon {
  vertical-align: round((1cap - 16px) / 2, 1px);
}

はい、これだけのコードでだいたいいい感じにアイコンとテキストが真ん中あたりで揃います。でもなぜこれで揃うのでしょう? 順に説明していきます。

round() 関数について

最初にこれから説明しておきますと、数値を選択した方法に従って丸めたものを返す CSS 関数です。別に calc() 関数だけで計算してもいいのですが、ブラウザによって端数ピクセルの処理が違うために環境によっては少しずれて見えたりするので、その処理を統一するために入れています。

丸め方、丸める値、丸め間隔の順で最大 3 つの引数を取ります。今回は丸め方を省略しているので、既定値の nearest となります3。後者 2 つには計算式 (<calc-sum>) がそのまま書けますので、別に calc() で囲う必要はありません。その他詳しくは例によって MDN の解説をお読みください。

ちなみに丸め間隔も省略できるんですが、その場合の既定値は数値 (<number>) の 1 です。なので今回のように単位のある値に使う場合には、たとえ単純な整数に丸めるだけでも 1px と書かないとエラーになってしまいます(最初数分これでハマった😭)のでご注意ください。

cap 単位について

cap というのは emex などと同様、その要素(あるいはその親要素)で現在使われているフォントを基準とした相対単位で、そのフォントのキャップハイト (cap height) に相当する長さ、分かりやすくいうとフォントの大文字の高さを 1 とする単位です。

ポイント: 高さ 1cap のボックスはベースライン揃えでテキストとキレイに揃う

で、フォントの大文字というのは基本的にベースラインに揃いますので、例えば幅と高さが 1cap のボックスを単純に ::before 擬似要素で生成してテキストと並べると、vertical-align を指定しなくても(デフォルトが baseline です)大文字とぴったり揃うことになります。これは実際に試してみると一目瞭然です。

p::before {
  content: "";
  display: inline-block;
  aspect-ratio: 1;
  height: 1cap;
  margin-inline-end: 5px;
  background-color: #000;
}

See the Pen 1cap height block by Jeffrey Francesco (@jforg) on CodePen.

そして、英字のグリフを含む日本語フォントは英大文字と小文字はもちろんのこと、かな文字や漢字についても英大文字とおおむね違和感なく縦位置が揃うようにデザインされてるはずです4。これはつまり 1cap サイズのボックスは、横にどんなテキストがきてもだいたいキレイに揃うことが期待できるということになります。上のデモにも色々な文字を当てた例を置いておいたので、確認していただければと思います。

ならばアイコンサイズと 1cap の差分を上下に均等に振り分けると…

さて、先ほど 1cap サイズのボックスはテキストとぴったり揃うと書きましたが、見方を変えればこれはボックスとテキストが縦の中央ラインで揃っている、ということでもあります5

だとすると 1cap サイズより大きい(あるいは小さい)アイコンがある場合にはその高さと 1cap の差分を計算して、その 1/2 をキャップラインとベースラインから上下に等しくはみ出させる(もしくは引っ込ませる)ことで、このアイコンもテキストと違和感なく縦の中央ラインに揃えられそうじゃないですか? ということで、そのロジックを実装してみたのがセクション冒頭の CSS コードです。

vertical-align プロパティに <length> 値を与えると、ベースラインからその長さ分ずれたところが揃え位置になるんでしたね(ここでえっ? となった人は 仕様を読みましょう)。16px のアイコンをテキストとベースラインで揃えると 1cap との差分がテキストより上にはみ出るので、その差分の 1/2 をマイナス方向(下側)にシフトすれば、ちょうど上下に半分ずつ均等にはみ出る…という具合です。

もしアイコンサイズが 1cap より小さい場合は差分がプラスになるので、この場合は同じコードで(数値を変更するだけで)上下均等に引っ込ませることができます。分けて考える必要はないです。

See the Pen ruby, small, sub and sup by Jeffrey Francesco (@jforg) on CodePen.

実際にやってみても、おおむね期待通りに動作しているようです。「おおむね」としてるのは、実際に色々なサイズで試してみると条件によってはどうしても 1 ピクセル程度のずれが生じてしまい、合ってないように感じられる場合があったからです😭 なのでまぁ、銀の弾丸ではないと思っておいていただけると…

応用: Flexbox の中でも使えます

「Flexbox を使わず中央揃え」という文脈の中で Flexbox の話をするのもなんですが😅 例えば Flexbox で align-items: center を使って縦中央揃えを行う場合、テキストが 2 行になるとその行間にアイコンがきてしまいますが、そうではなくてアイコンは常に最初の行の先頭であってほしい場面もありますね。

そういった場合には、アイコンとテキストは align-items: first baseline でテキスト先頭行のベースラインに揃えつつ、アイコンを 1cap との差分の半分上下移動させればいい訳です。ただしこの場合 vertical-align は使えませんので、アイコンを position: relative にして bottom(または inset-block-end)で上げ下げする感じになります。

p {
  display: flex;
  align-items: first baseline;
  gap: 1rem;
}
.icon {
  position: relative;
  bottom: round((1cap - 16px) / 2, 1px);
}

これでテキスト内容が 2 行以上、それこそものすごい長文になったとしても、常にアイコンをテキストの先頭に、いい感じで揃えておくことができます。

See the Pen icon alignment using the cap unit and vertical-align property by Jeffrey Francesco (@jforg) on CodePen.

欠点: アイコン画像のサイズを CSS でも指定する必要がある

こんな感じでうまいこと機能するというのはお分かりいただけたかと思いますが、欠点があるとすればアイコンのサイズを CSS の中でも指定する必要があるということでしょうか。カスタムプロパティにすれば解決…みたいな話ではなくて、

  • HTML で <img> 要素に widthheight を設定
  • CSS で round((1cap - ○○px) …) の○○(= height 属性値)を設定

というのが、ある意味二度手間である…という話です。同じことをわざわざ書くの面倒くさいじゃないですか。涙

いちおう CSS Values and Units Module Level 5 で追加された、新しい attr() 関数の構文を使うという方法があるにはあります。

.icon {
  /* 画像の `height` 属性の値を `px` 値だと解釈して返してくれる
     カンマ後の `16px` は `height` 属性がない場合のフォールバック */
  vertical-align: round((1cap - attr(height px, 16px)) / 2, 1px);
}

Chrome や Edge といった Chromium 系ブラウザではすでに使えるようになってますが、Firefox と Safari が未対応なので、まともに使えるようになるのはまだ数年先のことかと思います。なので当面は手作業でやるしかないですね。せいぜいサイズ別に class を分けるとか、カスタムプロパティで管理するくらいが関の山でしょう。ざんねん(´・_・`)

補足: ブラウザのサポート状況とフォールバック

ところで cap 単位は Baseline 2023, round() 関数は Baseline 2024 ということで、割と新しめの CSS 機能ではあるんですね。

普通に使ってもおおむね問題ないかとは思うんですが、それでもこれらの機能のサポートのない古いブラウザからのアクセスがあることはまだ十分に予想されることですので、もしも心配なのであれば何らかのフォールバックを書いておくのがいいと思います。

で、どうフォールバックするかですが、この例のような場合であればもう単純にスタイル当てた要素をブラウザでプレビューして、開発者ツールで拾った計算値をそのまま指定するのが一番楽でしょうね。何のアテもなくブラウザプレビューしながら 1px ずつ調整して「うーんこれくらいかな?」なんてやるよりは格段に効率がいいはずです。涙

.icon {
  vertical-align: -2px; /* 下のスタイルを当てたものをブラウザで見て、開発者ツールの「計算済み」で拾った値そのまま */
  vertical-align: round((1cap - 16px) / 2, 1px);
}

そんな訳で

cap 単位を使えば vertical-align プロパティでもテキストとアイコンの縦位置をいい感じに中央揃えできますよ、というお話でした!(雑な終わり方

  1. あとそれを解決しようとして img { vertical-align: middle } とやって「あれ、思てたんと違う」ってなるのも😭 

  2. CSS Grid でもいけますが、こういう場面(横並びした要素の縦位置だけの揃え)ではあまり使わない気がします。 

  3. 今回のように単純に整数値に丸めるだけの場合などは四捨五入と同等ですが、丸め間隔の指定によってはそうとも限りません。 

  4. 相当ダメなフォントデザイナーさんのものでもない限り😅 

  5. 上下が揃っているなら真ん中も揃ってて当然なので…