宣言ブロックのCSS設計

日本語で「CSS設計」を検索すると、記事やつぶやきなどでセレクタの命名規則に関する話題が多いです。
CSSを設計する上で、命名規則は重要な要素でしょう。
簡単なセレクタ名だと他のスタイルと重複する可能性もあります。他のスタイルと重複しないようにセレクタの子孫数を増やしてしまうと、今度はスタイルの取り回しが悪くなります。
またデザインをコンポーネントに分ける粒度について紹介されていますが、命名規則の分け方のように紹介されているよう感じます。

論理的に構造をわけて命名していくため、覚えやすく、伝えやすさもあわさって、現在の「CSS設計 = 命名規則」のような構図ができあがったと感じています。

CSS設計は命名規則だけか

命名規則はCSS設計において、重要な要素です。
しかしCSSは命名規則させ気を付ければ良い、というものではありません。

私は、すでにあるサイトの一部のコンテンツの作成やすでに用意されたコンポーネントを量産する仕事を受けることがあります。
その時に一定の規則で命名されていても「作業がしにくい」と感じることがあります。 その時は、だいたいCSSの「宣言」の選び方が下手です。

CSSは「セレクタ」と「宣言ブロック」に分けられ、「プロパティ」と「値」をまとめて「宣言」といいます。 命名規則によってセレクタのみが注目されますが、宣言ブロックもCSS設計には大切な要素の1つだと思っています。

CSS Wizardryから2012年に「Code smells in CSS」が公開されました。 この記事では、スタイルの取り消しやスタイルの可能性を制限する値、!importantなどについて記述されています。 2012年と古い記事ですが、今でも通用する内容です。

紹介した記事の内容からマジックナンバーについて、例を挙げましょう。この記事でのマジックナンバーとは「とりあえずうまく動いている値」です。

例として、コンテンツ部分やサイドナビゲーション、フォームなどさまざまなところで使われる「.button」というスタイルを作りしょう。.buttonというセレクタを作り、プロパティや値を設定しました。

.button {
  box-sizing: border-box;
  display: block;
  width: 400px;
  margin: 0 auto;
  border: 2px solid;
  padding: 16px;
  text-align: center;
  text-decoration: none;
  color: #3F51B5;
}

見た目は問題ないですが、上記のスタイルは悪い例です。 なぜならさまざまな所で使われるボタンという前提があるのにも関わらず、「width: 400px」という数値が指定されているからです。この数値がマジックナンバーです。

横幅を固定してしまうと、コンテンツ部分は良くてもサイドナビゲーションでボタンの横幅を修正しなければなりません。またレスポンシブWebデザインを行う時も、PCとスマートフォンなどで横幅を操作するCSSを追加する作業が発生します。

.buttonの宣言ブロックから、widthプロパティと値を外しましょう。CSSの記述は短く、どのような場所や場面でも柔軟に対応しやすくなるボタンのスタイルになります。ボタンを特定の横幅にしたければ、それ専用の追加のクラスを用意するようにしましょう。

ただし「どんな場所でもボタンは最大幅が400pxまで、400px以下のスペースにはボタンが収まるようにする」という条件があればどうすればいいいでしょうか?

この場合は、max-widthプロパティを使いましょう。 「width: 400px;」を使用すると横幅が固定されますが、max-widthプロパティを使用するば最大幅の400px以下のスペースにボタンをおいても、柔軟に横幅が変わります。
(ただしbutton要素やinput要素のsubmitだと、max-widthプロパティは効きません。「width: 100%;」を指定した上で「max-width: 400px;」と記述が必要です。)

.button {
  box-sizing: border-box;
  display: block;
  max-width: 400px;
  margin: 0 auto;
  border: 2px solid;
  padding: 16px;
  text-align: center;
  text-decoration: none;
  color: #3F51B5;
}

上記のCSSを使ったサンプル

ボタンという最小単位のUIを例に、マジックナンバーやプロパティなどについて説明しました。 プロパティと値を含めたCSSの宣言ブロックをおろそかにすると、スタイルの再利用性や柔軟性が失われ、無駄なセレクタやスタイルを増やす要因にもなります。

私が経験したサイトの中には、こちらが触れられないCSSに下記のようなa要素の宣言がありました。

a {
  color: #000 !important;
}

たった3行の指定ですが、a要素の文字色の変更やボタンのようなUIを実装しようとすると!importantばかりになります。 リセットCSSに!importantを付与すると、編集するほどにCSSが破壊され、制作者に精神ダメージを与える強力な呪いとなるでしょう。

このような指定は、CSSの命名規則がうまくいってるからといって、無視できるものではありません。 宣言ブロックの指定は、些細なものでもCSS設計の破綻させる要因となります。

1つの見た目でも複数のCSSパターンがある

WebデザインのレイアウトやUIなどは、CSSを使って構築します。 しかし1つのレイアウトであっても、CSS宣言の組み合わせは、いくつも考えられます。
2カラムにする方法も、以前に記事に書いた「CSSで2カラムを作ってみる」のように10パターン以上あるでしょう。

見た目の結果は同じでも、CSSの宣言の選択によって指定しているHTML要素はもちろん、他のHTML要素への影響度も変わります。
CSS設計を行う場合は、他の要素に与える影響やデザインの見た目や性質、環境にあわせて宣言の選択を行います。

宣言ブロックの指定

私がCSSで宣言ブロックを指定する時は、主に「パターンからCSSを構築」と「条件にあわせたCSSの構築」の2つの方法をとっています。
「パターンからCSSを構築」は経験で蓄積されたCSSのパターンから適したパターンを選択し、デザインと仕様などにあわせて宣言ブロックを最適化していく方法です。 レイアウトの構築など、ある程度決まった見た目に対してこの方法をとっています。

2つ目の「条件にあわせたCSSの構築」については、1から条件にあわせてCSSを作成していくスタイルです。
この「条件」とは、デザインやどのくらい柔軟なスタイルにするかなどを指します。
デザインを分解したら、宣言ブロックをを選び、デザインに合致するように最適化します。

全く異なるように見えて、どちらもセレクタの命名規則を考え、見た目に合わせてプロパティと値を設定するだけです。
そして最後にデザインや条件にあわせて宣言ブロックを最適化します。

デザインのフォントサイズが14pxであれば、その値を指定します。 背景があれば背景色を指定、角が丸ければborder-radiusプロパティを設定するだけです。
CSSプロパティの知識さえあれば、特に難しいことではありません。

ただCSSプロパティをよく理解していないがために、余計な記述や管理がしにくいスタイルを書いてしまう方もいます。

CSSプロパティを理解していない方で、よく見受けられるCSSの記述が「とりあえずCSSプロパティをぶっこんでみる」「何でもwidthプロパティを指定する」というのをよく見ます。
とりあえずCSSプロパティを指定しているのは「よく分からない」「たまに崩れるから」「スタイルの継承? 何それ?」など、理由はさまざまでしょう。
また何でもwidthプロパティを指定する多くの場合は、深い意味がなく、なんとなくやっていることでしょう。

以下のCSSに注意すれば、余計な記述をせずに、効率のいいスタイルを作成できるでしょう。

宣言ブロックで意識するポイント

各ボックスで使えるプロパティ

ブラウザはCSSの内容を表現する時に、視覚整形モデルのボックスを生成します。
このボックスは、CSSのdisplayプロパティによって決定されます。

displayプロパティがblockやlist-item, tableである時は、ブロックレベル要素。
displayプロパティがinlineやinline-blockである時は、インラインレベル要素です。
そしてブロックレベル要素はブロックレベルボックスを生成し、インラインレベル要素はインラインレベルボックスを生成します。それぞれのボックスにあわせて整形文脈が適用されます。

たまに勘違いされる方もいますが、HTMLのブロック要素やインライン要素は文章構造を指します。 対してCSSのブロックレベル要素やインラインレベル要素は、表示に使われるアルゴリズムです。
div要素に「display: inline;」の宣言が指定されていれば、HTML 4.01はブロック要素。
CSSではインラインレベル要素になります。

ブロックレベル要素かインラインレベル要素によって、扱えるプロパティやCSSの挙動が変わります。 分かりやすい例が、text-alignプロパティです。
ブロックレベル要素は文字を中央寄せにできますが、インラインレベル要素にtext-alignプロパティを指定しても中央寄せにはなりません。
CSSに余計な宣言を書いてしまう制作者は、このボックスの違いを理解せずに記述しているのが見受けられます。

.block {
  display: block;
  text-align: center;
}
.inline {
  display: inline;
  text-align: center;
}

使おうと思っているプロパティが、「全ての要素でも使えるのか」「ブロックレベル要素でなければ使えないのか」など覚えておきしましょう。

例えばspan要素にclearプロパティを設定しても、floatが解除されないといった経験はありませんか?
W3CのCSS 2.2 プロパティ一覧では、どのような条件でプロパティが使用できるか「Applies to」の列に記載されています。
clearプロパティであれば「block-level elements」となっており、ブロックレベル要素しか使えないと記述されています。そのため通常のspan要素はブロックレベル要素でないため、clearプロパティが使えません。

感覚的に分かっているプロパティもあるかもしれませんが、よく使うプロパティは一度目を通しておきましょう。

displayプロパティを意識する

各ボックスで使えるプロパティが違うことを踏まえて、あらためてdisplayプロパティを意識しましょう。
利用できるプロパティはもちろんですが、displayプロパティによって特性なども異なります。

例えばボタンが中央寄った下記のデザインを再現する時、ボタンに指定するdisplayプロパティの値は何がいいでしょうか?

  1. display: inline;
  2. display: inline-block;
  3. display: block;

どれを選んでも間違いではありません。 ボタンの親要素に「text-align: center;」宣言を行うなら、ボタンは「inline」や「inline-block」の値で中央に寄ります。
ボタンにwidthプロパティと「margin: auto;」宣言を使って中央に寄せるなら、displayプロパティの値は「block」になります。

1つの見た目でも、CSSの宣言の組み合わせはいくつもあります。 しかし、その組み合わせに制約や条件を付け加えていくと、利用する宣言は自然に絞られます。
文字数によってボタンの横幅が変わるという条件を付け加えたとします。

文字数に合わせて、その都度widthプロパティの値を指定するのは手間です。そのためdisplay: block;の宣言は除外されます。
そしてボタンを挿入する場所によっては、コンテンツ幅よりも文字数が多くなってしまうかもしれません。その場合は、display: inline;ではボタンの形がくずれてしまうため、こちらの宣言も除外されます。

3つのdisplayプロパティの値から選ぶのであれば、最終的にdisplay: inline-block;の宣言がいいでしょう。
しかしこの選択は「文字数によってボタンの横幅が変わる」という条件を付け加えた結果でしかありません。
条件が「ボタンは常にコンテンツ幅に準ずる」とするならinline-blockではなく、blockの値の方がいいかもしれません。

デザインの見た目や性質によって、displayプロパティを使い分けが必要になります。
なお、ブラウザのUAスタイルシートで定義されている宣言と指定したいdisplayプロパティが合致していれば、displayプロパティを無理に付け加える必要はありません。
余計な記述を増やさないためにも、ある程度UAスタイルシートを理解することも大事です。

スタイルの継承

body要素にcolorプロパティを設定すると、div要素やp要素などにも文字や線の色が変わります。これはcolorプロパティの値を子の要素が継承しているからです。 CSSには親要素から子要素へ値を継承するプロパティがあります。

HTML

<body>
  <div>
    <h1>Heading</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dicta voluptatum nihil ipsa, nulla repudiandae, consequuntur libero, corporis voluptas quis eligendi recusandae. Harum debitis consequatur, ducimus! Deleniti beatae sed repellendus possimus.</p>
  </div>
</body>

CSS

body {
  color: #f00;
}
p {
  border: 1px solid;
}

上記のCSSはbody要素にcolorプロパティを設定したので、子孫の要素にcolorプロパティが継承され、文字や線の色が赤に変わりました。

どのプロパティがスタイルの継承を行うかは、「各ボックスで使えるプロパティ」で紹介したCSS 2.2 プロパティ一覧の「Inherited」の列から確認できます。
継承プロパティは「yes」、非継承プロパティは「no」と記載されています。

CSS設計を行う場合は、継承を行うプロパティは特に意識しましょう。プロパティは無闇に指定してまうと、思わぬところでハマってしまう場合があります。
下記のようなCSSは、考えなしに宣言ブロックを指定した例です。

HTML

<div class="contents">
  <div class="heading">
    <h1>見出し</h1>
  </div>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Et perferendis dolor officia adipisci repudiandae iure nostrum voluptatibus accusamus tempora vero esse, tenetur alias pariatur consequatur soluta, sint modi! Velit, aliquid?</p>
</div>

CSS

.contents p {
  margin: 1em 0;
  color: #222;
  text-align: left;
}
.heading {
  margin-bottom: 2em;
  text-align: center; 
}

上記のコードは見出し部分が中央寄せ、段落が左寄せになるように指定されています。見た目は問題ありません。
しかし後から見出しの下に中央寄せでリード文を追加する依頼が来ると、どうでしょう。
headingクラスの付いたdiv要素の中に、p要素でリード文を追加して画面を確認しましょう。

HTML

<div class="contents">
  <div class="heading">
    <h1>見出し</h1>
    <p>リード文を追加</p>
  </div>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Et perferendis dolor officia adipisci repudiandae iure nostrum voluptatibus accusamus tempora vero esse, tenetur alias pariatur consequatur soluta, sint modi! Velit, aliquid?</p>
</div>

本来なら親要素に「text-align: center; 」宣言が指定されていれば、継承によって子の要素も中央寄せになります。
しかしリード文が左に寄ったままです。

理由はCSSの「.contents p」セレクタに「text-align: left;」宣言が指定されているからです。通常、何もしなければ文字は左寄せになります。
「.contents p」セレクタの「text-align: left;」宣言は、必要のない宣言です。
無駄な記述を行なっているせいで、別の要素にまで影響します。

リード文を中央寄せには、「.contents p」セレクタの「text-align: left;」宣言を削除するだけで達成できます。

最適化したCSS

.contents p {
  margin: 1em 0;
  color: #222;
  /* 削除: text-align: left; */
}
.heading {
  margin-bottom: 2em;
  text-align: center; 
}

無駄な継承する宣言を削除するだけで、見た目も正常になり、CSSの記述量も減らせます。
もし「text-align: left;」宣言を削除せずに、下記のようなスタイルを追加していくと肥大化しやすいCSSになります。

「text-align: left;」宣言を削除していないCSS

.contents p {
  margin: 1em 0;
  color: #222;
  text-align: left; /* 削除しない */
}
.heading {
  margin-bottom: 2em;
  text-align: center;
}
.heading p { /* スタイルを、さらに追加しちゃう */
  text-align: center;
}

紹介した例は、限定的な箇所なので修正しやすいです。
しかし中規模や大規模なサイトで継承を行うプロパティを放置していると、宣言ブロックが複雑化してきます。 最終的には、CSSがさまざまなところに影響しあい、CSSの肥大化を招きます。

スタイルの継承を意識したCSS設計を行い、CSSの複雑化を防ぎましょう。

widthプロパティが必要か考える

最初の章でボタンを使ったwidthプロパティを例に挙げました。
widthプロパティを取るだけでコンテンツに合わせて伸縮したボタンになるとを解説していますが、もう少し掘り下げましょう。

div要素に線を指定すると、コンテンツ幅いっぱいまでdiv要素が伸びているのを確認できます。

これはCSSのデフォルトで設定されているブロックレベル要素に指定されたwidth: autoによる効果です。ブラウザのウィンドウやコンテンツ幅を変化させても、borderやmarginを追加しても、div要素がコンテンツ幅に合うように計算されます。
(詳しく知りたい方は、10.2 コンテンツの幅:'width'プロパティ | 10 視覚整形モデル詳細を参照。)

いくつか条件もありますが、通常のブロックレベル要素はコンテンツ幅にあわせて伸縮されるため、無理にwidthプロパティを付ける必要はありません。

例えば、大見出しよりも本文などが左にスペースの空いた見た目を作成する場合。このような見た目を作成する時に、本文の親要素にwidthプロパティを指定される方がいます。

この場合は、widthプロパティのない方がCSSの記述も減りますし、修正なども行いやすくなります。 見出し2と本文をdiv要素で囲み、その要素にmarginかpaddingプロパティを指定するだけで要素は自動で伸縮し、コンテンツ幅に収まるでしょう。

柔軟性の高いCSSを作るのであれば、本当に必要な要素のみにwidthプロパティを指定するようにしましょう。
多くの場合は、marginやpaddingプロパティのみである程度、調整ができるはずです。

設計したCSSの影響

CSSは宣言ブロックによって、他のスタイルに影響を及ぼす場合があります。

間隔は、読みやすさや要素の結びつきなどに影響する要素です。CSSでは、この間隔の指定がデザインを再現する上で、デザインや他の要素に影響与えてしまう場合があります。

例えば、見出しのmarginプロパティは0にしている時、p要素に上方向に指定すると見出しとの間隔をうまく空けられます。

しかし見出しなど、上に要素がなくなるとコンテンツ部分が下がって見えてしまいます。

下方向に間隔を指定すると上は良くても、下の間隔が空いてしまいます。そのため親要素の余白を調整することもあるでしょう。

間隔の指定は繊細です。
CSS設計を考える時はデザインに従い、間隔のための最適な宣言ブロックを決定します。

設計したCSSでは、なるべくCSSを意識せずにHTMLがマークアップでき、イレギュラーなコンポーネントを入れても楽に調整できるようにCSSを設計することが大事です。

例えば段落などの間隔を決める方法でも、いくつも考えられます。
下記は一部ですが間隔の空け方にはそれぞれの特性があり、デザインによってCSSの指定の仕方を変えることを考えましょう。

  • 上方向
  • 下方向
  • 上下方向
  • 打ち消しによる方向指定
  • 隣接・間接セレクタによる上方向

上方向
見出しなど、段落の前に必ず要素がある場合に有効です。 上方向に指定するので余計な余白を出しませんが、段落から始まるようなイレギュラーに弱いです。

p {
  margin-top: 8px;
}

下方向
上に余白が生まれないため、色々な場面で使いやすいです。 ただしボックスで囲まれたデザインなどでは、余白が生まれるため、調整が必要にります。

p {
  margin-bottom: 8px;
}

上下方向
UAスタイルシートなどで採用されているmarginの相殺を利用した間隔の空け方です。
イレギュラーなコンポーネントが入っても必ず一定の間隔を空けられますが、他の要素との関係性などを綿密に考える必要があります。

p {
  margin: 8px 0;
}

打ち消しによる方向指定
擬似クラスやクラスによって、指定した方向を打ち消します。指定したものを、打ち消して実現するため、冗長的になりやすいのが欠点です。 しかし擬似クラスの特性やクラスによる変則的な指定ができます。

p {
  margin-top: 8px;
}
p:first-child {
  margin-top: 0;
}

隣接・間接セレクタによる上方向
打ち消しによる方向指定とほぼ同じ見た目で、CSSが短くなる傾向があります。

/* 隣接セレクタ */
p + p {
  margin-top: 8px;
}

/* 間接セレクタ */
p ~ p {
  margin-top: 8px;
}

上記の間隔の空け方から、いくつか例をご紹介します。

間隔の指定方向

文章ベースのコンテンツ場合は、どれを採用しても構いません。
本ブログは特別な装飾も少ないため、marginの相殺を生かした間隔の上下方向の指定を行なっています。 下記は、本ブログのCSSの一部です。

h2, h3, h4, h5 {
  margin: 3em 0 1em;
  line-height: 1.3;
  letter-spacing: 0.05em;
}

h1 + h2,
h2 + h3,
h3 + h4,
h4 + h5 {
  margin-top: 0;
}

p, figure {
  margin: 1.55em 0;
}

marginは、子孫関係か兄弟関係にある要素でmarginを打ち消す特性があります。
とある要素に上下marginを16pxを指定して、HTML要素を並べると、その間隔は、上と下のmarginを足した32pxではなく「16px」になります。

もし上marginが16px、下marginが40pxであれば、要素を並べた時の間隔は、大きい方のmarginが適用されて「40px」になります。

marginの相殺は嫌われることも多いのですが、うまく利用すれば、それぞれの要素の間隔を最低限確保して見た目を構築できます。
私は、コンテンツの下部に全ページで共通のコンポーネントがあった場合、その間隔を一定にするために、marginの相殺を利用する場合もあります。

ただしCSSをよく理解していないのであれば、上下方向ではなく、単一方向にmarginを揃えましょう。floatやoverflowプロパティなど、相殺が起こらない条件が発生すると、意図した見た目にならないかもしれません。

marginの相殺を利用したCSSの設計は利点もあるのですが、CSSの知識レベルが異なるチームで作成する場合はmarginを単一方向に指定した方が混乱は少ないでしょう。

打ち消しと追加

背景の敷かれた要素の中にカードが入ったようなデザインなど、たまに見かけます。このカードに一定方向のmarginを指定すると、指定した方向の親要素に余計な余白が空いてしまいます。

これを解決するには指定した一部の宣言を打ち消すか、セレクタを使って宣言を追加する方法があります。
marginの指定方向はそれぞれ上方向で指定し、見た目は全く同じになりますが、2つの方法を見比べましょう。

打ち消しは色々な方法があり、本記事ではそれぞれのカードの要素に上方向のmarginを指定します。
このままでは上に余白ができるので、:first-child擬似クラスを使ってmarginを0にします。

.card {
  margin-top: 16px;
}
.card:first-child {
  margin-top: 0;
}

:first-child擬似クラスを使ったHTML

宣言を追加する方法は、今回は後続する要素にマッチする隣接セレクタを使います。
打ち消しに比べて、間隔を空けるための宣言が1つで済みます。 このような見た目は、隣接セレクタなどを使って、宣言する方法をおすすめします。

.card + .card {
  margin-top: 16px;
}

隣接セレクタを使ったHTML

ただし:first-child擬似クラスは、隣接セレクタにない利点もあります。
ボックスの中にp要素ごとに間隔を空けました。CSSと見た目は下記の通りです。

.type-A p {
  margin-top: 16px;
}
.type-A p:first-child {
  margin-top: 0;
}

.type-B p + p {
  margin-top: 16px;
}

異なるCSSの指定で同じ見た目を再現しています。 このHTMLのボックスに見出しを設置しましょう。見出しのmarginプロパティは0になっています。

見出しが入ると、見た目に差が出ました。
:first-child擬似クラスは、見出しとp要素との間隔が確保されます。 対して隣接セレクタは、見出しとp要素との間隔が詰まっています。

:first-child擬似クラスは、最初の要素にマッチします。見出しが入ると最初の要素はp要素ではなく、見出しの要素になります。 そのためp要素の打ち消しがマッチせずに、見出しとの間に間隔を空けられます。

どちらの指定が正しいということはありません。 デザインによっては見出しと本文が詰まったデザインもありますし、見出しと本文が適度に空いたデザインもあります。
間隔の指定は状況にあわせて判断し、HTMLの特別なクラスやCSSを追加しなくても、うまく動作するように心がけてCSSを設計するようにしましょう。

余白をどこに付けるか?

下記の条件で左右の余白を付ける時に、HTMLのどこに余白を付ければいいでしょう。

下のHTMLを使って見た目を再現してください。
左右の余白を20pxで、HTMLは修正できないものとします。

<div class="contents">
  <div class="cover"><img src="cover.jpg" alt=""></div>
  <h2>見出し</h2>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Incidunt veritatis, ipsum ducimus!</p>
  <p>Officia necessitatibus eligendi consequatur magni similique velit suscipit sit numquam assumenda ex.</p>
  <p>Tenetur sit, necessitatibus? Ipsa expedita adipisci velit necessitatibus vel, a, animi delectus.</p>
</div>

写真がコンテンツ幅いっぱいに伸びているため、このHTMLを使った場合、coverクラスは画面いっぱいまで伸びるようにして、p要素などにpaddingプロパティを付けてしまう方もいるかもしれません。

.cover img {
  width: 100%;
}
.contents h2 {
  padding: 0 20px;
}
.contents p {
  padding: 0 20px;
}

私がCSSを作成する時は、物事を大きく捉えて、細かい修正でもなるべく柔軟に対応できるように意識しています。 この場合の起こりえる修正は、コンテンツの中身の変更です。

提示したHTMLと見た目では、コンテンツの中には見出しとp要素しか入っていません。 しかし別のページでは、リストや別のコンポーネントが入るかもしれません。このページの修正依頼も入るかもしれません。

指示された見た目は再現できても、もしリストや別のコンポーネントが入った場合、それぞれに新しく余白の指定を行うのは面倒臭いでしょう。 過去に携わった案件では、このような左右の余白を段落などにつけているせいで修正するのが、大変なCSSをいくつも見ています。

私がやるとしたら、親の要素に余白を設けます。 親の要素に余白を付けると、コンテンツの中にさまざまなコンポーネントが入っても左右の余白は必ず確保されます。

写真がコンテンツ幅いっぱい広がっていますが、「左右の余白を20px」と事前に確定した指示があるので、こちらはマイナスmarginで指定してあげましょう。
下記は親要素に余白を設定したCSSです。スタイルごとに役割がはっきりするので、管理もしやすくなります。

.cover { /* 役割: コンテンツ幅の余白分、広げる */
  margin: 0 -20px
}
.cover img { /* 役割: 写真をコンテンツ幅いっぱい広げる */
  width: 100%;
}
.contents { /* 役割: コンテンツに左右の余白を設ける */
  padding: 0 20px;
}

上記のCSSを指定したHTML

余白に限ったことではありませんが、宣言ブロックの指定が下手なCSSは1つのスタイルに色々詰め込み過ぎています。
この例ではHTMLが制限されていましたが、必要であればHTMLも追加して柔軟性を保つように心がけましょう。

最後に

「宣言ブロックのCSS設計」というタイトルですが、全て当たり前のことです。 ご紹介したのは一部であり、新しい情報は何もありません。

命名規則やコンポーネントの分け方も大事ですが、「CSS設計 = 命名規則」という構図に、常に違和感を感じていました。
セミナーやイベントの懇親会などで「CSS設計をどうしていますか?」と問われることもあります。
しかし求められている答えは「どんなCSSの命名規則を使っているか?」ということが多く、「CSS設計にはプロパティや値の選定も必要」と説明しても、伝わらないことが多々ありました。

これらから、より広い目でCSS設計が行われることを願っています。