見出しは文書構造を決定する重要な要素
セクションが入れ子になり、情報の階層が1つ下がったら、そのセクションの見出しレベルも1つ下げるというように、情報の階層構造に応じ適切にレベル(h1~h6)を使い分ける必要がある。
デザイン面では、同じレベルの見出しは同じデザインで統一することで視覚的にも情報の区切りを明示しデザインの統一感も担う重要な役割を持っているため、一般に見出しレベルとそれに対応するスタイルは一致しているというのがデザインにおける常識である。
しかし、実際にはHTMLで表現する情報の階層構造に伴う見出しレベルと、デザイン表現上の見出しスタイルは必ずしも一致するとは限らない。そこに見出し設計の難しさがある。
<section class="section">
<h2 class="heading-lv2">レベル2大見出しテキスト</h2>
<p>この文章はダミーです。</p>
<section class="sub-section">
<h3 class="heading-lv3">レベル3中見出しテキスト</h3>
<p>この文章はダミーです。</p>
</section>
</section>
見出しの構造とスタイルは常に一致するわけではない
コンポーネントは様々な場所で再利用される可能性があるため、元の場所と再配置先は情報の階層が異なるということもありえる。階層が異なれば基本的に見出しレベルも変更する必要があるため、そういった意味でも見出しレベルに直接スタイルを当てていると問題が発生する頻度が非常に高くなる。
BEMを始めとするあらゆるCSS設計では常に「要素に対して直接スタイルを指定してはいけない」と言われるが、その事例として一番に挙げられるほど、見出しは特に要素とスタイルが一致しない典型的なコンポーネントである。
<section class="section">
<h2 class="heading-lv2">レベル2大見出しテキスト</h2>
<p>この文章はダミーです。</p>
</section>
<aside class="aside-section">
<h2 class="heading-lv3">補足セクションの見出し(h2)</h2>
<p>この文章はダミーです。</p>
</aside>
/* Lv2 */
.heading-lv2 { /* h2に直接スタイルを当ててはいけない */
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 40px;
font-size: 28px;
line-height: 1.5;
}
.heading-lv2::after {
content: "";
display: block;
width: 40px;
margin-top: 20px;
border-top: 1px solid;
}
/* Lv3 */
.heading-lv3 { /* h3に直接スタイルを当ててはいけない */
margin-bottom: 20px;
padding-left: 1em;
border-left: 4px solid #558ebd;
font-size: 22px;
line-height: 1.5;
}
見出しの下にサブタイトルが付く場合
CSS設計する際に気をつけるべきポイントは、「サブタイトルがないパターンも想定する」という点。
まず見出し自体の罫線装飾は、サブタイトルの有無に関わらず必須の装飾要素なので、親Blockのheading-lv2のafter疑似要素で表現する。また、罫線からテキストの下端の余白についても、after要素のmargin-topで設定しておく。サブタイトルのmargin-bottomで設定してしまうと、サブタイトルがなかった場合に適切な余白が維持できないためである。
メインタイトルとサブタイトルの間は10pxの余白を取るが、下にサブタイトルがある場合、この余白はサブタイトル側のmargin-topに付けるようにしておく。サブタイトルとメインタイトルの間の余白はサブタイトルがなければ不要となるものなので、余白もセットにしておいた方が分かりやすい。
また親Block自体をdisplay: flexにしてレイアウトしているが、この場合flexアイテムである子要素同士はmargin相殺が効かなくなるため、メインタイトル側のmargin-bottomに10pxを付けてしまうと、サブタイトルがない場合に隣接する罫線のmargin-top: 20pxと足されて30pxの余白となってしまう物理的な問題も生じてしまう。
<h2 class="heading-lv2">
<span class="heading-lv2__main">レベル2大見出しテキスト</span>
<span class="heading-lv2__sub">サブタイトル</span>
</h2>
.heading-lv2 {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 40px;
line-height: 1.5;
}
.heading-lv2::after { /* 罫線は必須なので親の疑似要素で設定する */
display: block;
content: "";
display: block;
width: 40px;
margin-top: 20px; /* サブタイトルの有無に関わらず罫線上の余白を確保する */
border-top: 1px solid;
}
.heading-lv2__main {
font-size: 28px;
}
.heading-lv2__sub {
margin-top: 10px; /* サブタイトルとその上の余白をセットにしておく */
font-size: 16px;
font-weight: normal;
}
見出しの上にサブタイトルが付く場合
サブタイトル(意味的にはタイトルではなく短いキャッチコピー的なものである場合もある)がメインタイトルの上にある場合も、取り外しを想定した余白設定をしておくことが重要である。
サブタイトルが上にある場合、サブタイトルとメインタイトルの間の余白についてはサブタイトル側のmargin-bottomで設定しておく。こうすればサブタイトルがなくなった場合には余白も一緒に消えるので、見出し自体のスタイルに影響を与えなくて済む。
また、隣接セレクタを利用することでメインタイトル側のmargin-topで設定することも可能。
パーツの取り外しが可能となるように組む場合、隣接する要素との余白をどこに付けたら余白も一緒に削除されるのか、よく検討するようにする。
<h2 class="heading-lv2">
<span class="heading-lv2__sub">サブタイトル</span>
<span class="heading-lv2__main">レベル2大見出しテキスト</span>
</h2>
.heading-lv2__sub {
margin-bottom: 10px; /* サブタイトルとその下の余白をセットにしておく */
font-size: 16px;
font-weight: normal;
}
.heading-lv2__sub + .heading-lv2__main {
margin-top: 10px;
}
マークアップのパターン
<!-- ①まとめてh2とする -->
<h2 class="heading-lv2">
<span class="heading-lv2__main">レベル2大見出しテキスト</span>
<span class="heading-lv2__sub">サブタイトル</span>
</h2>
<!-- ②h2とpに分割する -->
<div class="heading-lv2">
<h2 class="heading-lv2__main">レベル2大見出しテキスト</h2>
<p class="heading-lv2__sub">サブタイトル</p>
</div>
①のようにh2などの見出し要素の中をspan要素で分割した場合、見た目上はメインタイトルとサブタイトルを別物として扱うことができるが、文書のアウトライン上に出現する見出しテキストは「メインタイトルサブタイトル」という連結された1つの文字列となる。明確に2つで1つの見出しとして見せたいといった意図がある場合はこのパターンで良い。
②は見出しブロックをdivで囲み、メインタイトルのみをh2、サブタイトルはpでマークアップする案。こちらはサブタイトルのほうは見出し要素から外れているので文書のアウトライン状に現れるのはh2のメインタイトル文言のみとなる。サブタイトル側が見出しを補足するテキストであったり、キャッチコピー的な文章なのであれば、そこまで見出しとしてアウトライン上に載せるのはやりすぎであると思われるため、メインタイトルのみを見出し要素としておくのが適切である。(※②の場合、セクション要素を明示したマークアップであるならdivではなくheader要素を使った方が文書構造をよりセマンティックに表現できる)
見出しの周囲に付属パーツがあることが最初から分かっている場合
比較的よくあるのが見出しの右端に一覧や詳細へのリンクボタンが付くケース。この手の見出しは、リンクボタンがあったりなかったりどちらもありえると考えた方が良いので、見出し本体とリンクボタンは別Blockとし、取り外し可能な状態にしておく必要がある。
マークアップ面での注意点は、見出し要素の中に直接リンクボタンを入れてしまわないこと。一覧リンクのボタン自体は明らかに「見出し」ではないからである。
スタイル指定する時の注意点は以下の3点。
- リンクボタンは取り外し可能であること
- 見出しテキストが長くなった場合にボタンとテキストが被らないこと
- リンクボタンがなければ端まで全てテキストが入る領域となること
<div class="heading-lv2">
<h2 class="heading-lv2__title">レベル2大見出しテキスト</h2>
<p class="heading-lv2__btn"><a href="#" class="btn btn--xsmall">一覧へ</a></p>
</div>
.heading-lv2 { /* 見出し要素の親Blockに枠スタイルを指定 */
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40px;
padding: 10px 0 10px 20px;
border-left: 4px solid #558ebd;
border-bottom: 1px solid #ccc;
line-height: 1.5;
}
.heading-lv2__title { /* 見出し要素にはテキストスタイルのみ指定 */
font-size: 28px;
}
.heading-lv2__btn {
flex-shrink: 0; /* テキストが長くなった時でもサイズが変わらないようにする */
margin-left: 20px; /* テキストとボタンの間に適切な余白を維持する */
}
見出しの周囲に付属パーツがあることが後から判明した場合
この場合、ボタン付き見出しレイアウトを再現するためのブロックを追加し、既存の見出し要素の上にposition: absoluteでボタンを被せるようにするのが一番簡単な方法と思われる。
ただしabsoluteで被せる場合は見出しテキストとボタンが被らないように、見出しの右側にボタン領域分の余白を確保しておく必要がある。
absoluteで上からボタンを被せるパターンは、簡単ではあるが「ボタンのサイズと見出しのテキスト領域のサイズが連動しない」という仕様上の弱点がある。
これを避けるためには、ボタン付き見出しレイアウト用の親ブロックを追加した場合だけ、見出し要素自身に付けてある見出し枠スタイルを打ち消して親ブロックに移動させ、flexでレイアウトするようにしておくしかないと思われる。
<div class="heading-lv2-wrap">
<h2 class="heading-lv2-wrap__title heading-lv2">レベル2大見出しテキスト</h2>
<p class="heading-lv2-wrap__btn"><a href="#" class="btn btn--xsmall">一覧へ</a></p>
</div>
.heading-lv2 { /* 見出し要素に直接スタイルを指定してある */
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40px;
padding: 10px 0 10px 20px;
border-left: 4px solid #558ebd;
border-bottom: 1px solid #ccc;
line-height: 1.5;
font-size: 28px;
}
/* あとから付属要素があるパターンを追加(絶対配置) */
.heading-lv2-wrap {
position: relative;
}
.heading-lv2-wrap__btn { /* 絶対配置で右端に配置 */
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
}
.heading-lv2-wrap__title { /* テキストがボタンに被らないように余白を追加 */
padding-right: 100px;
}