本文エリア内の要素をpaddingのないコンテナとして実装する

後日追記: このやり方の改良版をブログ「ブロックエディタ用のCSSを参考にした、よりよいコンテナの実装」として書いた。


CSSにおいて、コンテナと呼ばれるパターンがある。たとえば次のような実装を指している。

.container {
  box-sizing: content-box;
  margin-right: auto;
  margin-left: auto;
  padding-right: 1.25rem;
  padding-left: 1.25rem;
  max-width: 60rem;
}

この実装の特徴は、ビューポートの幅が広いときには要素の内容物が中央揃えになり、狭いときには左右に最低限の余白を残しつつ縮むことだ。

よくある使い方としては、このコンテナでページの外側の方を囲いつつ、その中に雑多に要素を配置していくことになる。

<div class="container">
  <h2><!-- ... --></h2>
  <p><!-- ... --></p>
  <p><!-- ... --></p>
</div>

しかし、このコンテナの幅よりも広い幅で何かを配置したいという場合には、コンテナの中に配置すると都合が悪い。そういう場合には、そのコンテンツをコンテナの外側に配置できると望ましい。

<div class="container">
  <h2><!-- ... --></h2>
  <p><!-- ... --></p>
  <p><!-- ... --></p>
</div>

<figure class="wider-than-container"><!-- ... --></figure>

<div class="container">
  <p><!-- ... --></p>
  <p><!-- ... --></p>
</div>

ただし、いつでもこのように自由にHTMLを構成できるとは限らない。

たとえば、CMSによってユーザーに入力された記事の本文部分を表示する場合。出力されるHTMLの形式はある程度決まっていて、カジュアルに変更するのは難しいときがある。

前述の例の場合、HTMLとしては次のような形を取らざるを得ないかもしれない。

<div class="container">
  <h2><!-- ... --></h2>
  <p><!-- ... --></p>
  <p><!-- ... --></p>
  <figure class="wider-than-container"><!-- ... --></figure>
  <p><!-- ... --></p>
  <p><!-- ... --></p>
</div>

CMSから出力されるHTMLを、コンテナの幅ごとに分割することはできないので、このままの構造に従ってスタイルを適用しなければならない。

その場合、こうした本文エリアの全体をコンテナで囲ってしまうと、幅の実装をするのが難しくなる。そこで、その代わりとして、出力されるHTMLに含まれる要素の一つ一つに直接幅を指定する、というやり方がある。

<div class="prose">
  <h2><!-- ... --></h2>
  <p><!-- ... --></p>
  <p><!-- ... --></p>
  <figure class="wider-than-container"><!-- ... --></figure>
  <p><!-- ... --></p>
  <p><!-- ... --></p>
</div>
.prose > * {
  box-sizing: content-box;
  margin-right: auto;
  margin-left: auto;
  padding-right: 1.25rem;
  padding-left: 1.25rem;
  max-width: 60rem;
}

.prose > .wider-than-container {
  max-width: 75rem;
}

こうすると、幅に関する実装が簡単になる。

しかし、要素にpaddingが適用されていると、別のスタイルとの組み合わせに支障をきたすことがある。

たとえば、見出しに下線が引かれているようなあしらいを実現するためにborder-bottomを使うとすれば、下線の描画範囲がpaddingのせいで左右に余計に広がってしまう。また、背景色がついている要素などの内側でpaddingを使いたくても使えなくなってしまう。あるいは、position: absoluteを使って、要素の相対位置に何かを配置したいというときに、X軸の基準位置が外側にずれてしまう。

こうした問題は、何かしらのテクニックによって回避できるかもしれないが、少なくともpaddingがないに越したことはないだろう。

そういったわけで僕は、あえてpaddingを使わずにコンテナを実装することがある。次のような感じだ。

.container {
  --_container-margin: 1.25rem;
  margin-right: auto;
  margin-left: auto;
  width: calc(100% - var(--_container-margin) * 2);
  max-width: 60rem;
}

これを応用すると、本文エリア内でも要素の幅が異なるような実装ができる。

<h1 class="container"><!-- ... --></h1>

<div class="prose">
  <h2><!-- ... --></h2>
  <p><!-- ... --></p>
  <figure><!-- ... --></figure>
  <p><!-- ... --></p>
  <figure class="alignwide"><!-- ... --></figure>
  <p><!-- ... --></p>
  <figure class="alignfull"><!-- ... --></figure>
  <p><!-- ... --></p>
</div>
.container {
  --_container-margin: 1.25rem;
  margin-right: auto;
  margin-left: auto;
  width: calc(100% - var(--_container-margin) * 2);
  max-width: 60rem;
}

.prose > :not(.alignfull) {
  --_container-margin: 1.25rem;
  margin-right: auto;
  margin-left: auto;
  width: calc(100% - var(--_container-margin) * 2);
  max-width: 45rem;
}

.prose > .alignwide {
  max-width: 60rem;
}

具体的なイメージが伝わりやすいように、もう少し複雑な、よくある記事ページの実装例も用意した。