結論
プロは影を「1枚の濃い黒」で済ませない。ブラーとオフセットを倍々に増やした5〜6層のレイヤード影で実世界の半影(ペナンブラ)の減衰を再現し、影色は純黒を捨てて背景の色相を借り、ページ全体で光源を1つに固定する(縦オフセット=横オフセット×2)。浮き上がり(elevation)はオフセット・ブラー・不透明度の3パラメータを同時に動かして表現し、ダークモードでは影でなく面を明るくして高さを出す。
01 — 単一影をやめ、5層に重ねる
box-shadow のぼかし関数は1つだけ。1枚のぼけたシルエットを貼るだけなので、実世界の影にある減衰グラデーションが欠落し「四角く貼り付けた」安っぽさになる。Tobias Ahlin の言葉では square で clumsy。代わりにオフセットとブラーを 1→2→4→8→16px と倍々に増やした5層をスタックすると、わずかに異なるぼかしが重なって滑らかなペナンブラが生まれる。
primarySmoother & sharper shadows with layered box-shadows | Tobias Ahlin
02 — 影色は黒でなく背景の色相に寄せる
hsl(0 0% 0% / .4) のような透明黒は下地を脱彩(desaturate)させ、washed-out なグレーの濁りを生む。影が画面から浮いて雑に見える原因がこれ。背景の色相を借り、彩度は中程度・明度は下げた色を --shadow-color に入れて一括管理すれば、彩度を保ったまま自然に沈む。下の例はどちらも同じ青タイル上、同じ3層構成で影色だけが違う。
primaryDesigning Beautiful Shadows in CSS — Josh W. Comeau
03 — 光源を1つに固定する(縦=横×2)
要素ごとに影の方向や縦横比が違うと、複数の光源が存在するように見えて一気にフェイクになる。ページ上の全ての影が同じ縦横オフセット比を共有すると、遠くの単一光源(太陽のような)から照らされた一貫性が出る。Comeau の慣習は縦オフセット=横オフセットの2倍(1/2、2/4…)。下の左は3枚がバラバラの方向、右は全て右下・縦=横×2で統一。
primaryDesigning Beautiful Shadows in CSS — Josh W. Comeau
04 — elevation は3パラメータを同時に動かす
浮き上がりをオフセットだけ、あるいはブラーだけで表現すると不自然。実際はオフセット拡大・ブラー拡大・不透明度減少が必ず同時に起こる。低い要素は単層で小さく濃く(opacity .7)、高い要素は多層で大きく薄く(.2)。これで low / mid / high の3段スケールを作る。下の左は3枚とも同じ影でelevation差が出ていない例、右は3段で距離が読み取れる例。
primaryDesigning Beautiful Shadows in CSS — Josh W. Comeau primaryLight and shadows — Material Design (M2)
05 — ダークモードは影でなく面を明るくする
暗い面の上では黒い影はほぼ視認できず、深さが伝わらない。Material(M2)は #121212 を基準に白オーバーレイを重ねて高さを表現する(1dp=5%、8dp=12%、24dp=16%)。ライト用の影トークンを使い回すとダークでのっぺりする。下はどちらも暗いキャンバス上、左は黒影(ほぼ見えない)、右は白12%オーバーレイで面を持ち上げた例。
primaryDark theme — Material Design (M2) primaryIntroducing Tone-based Surfaces in Material 3
06 — 非矩形・透過画像は drop-shadow を使う
box-shadow はバウンディングボックス(外接矩形)に影を落とすため、透過部分のあるアイコンや切り抜き画像では形に沿わない四角い影が出る。filter: drop-shadow() なら要素の実形状(切り抜き含む)に沿って影が落ちる。下は同じ星形に同じ影パラメータを適用し、左が box-shadow、右が drop-shadow。
primaryDesigning Beautiful Shadows in CSS — Josh W. Comeau
実装スニペット
レイヤード影の基本形(Tobias Ahlin・opacity一定)。オフセット・ブラーを倍々にし、全層 .12 でニュートラルな滑らかさ。シャープにしたいときは上から .25/.20/.15/.10/.05 に振る。
.card {
box-shadow:
0 1px 1px rgba(0,0,0,0.12),
0 2px 2px rgba(0,0,0,0.12),
0 4px 4px rgba(0,0,0,0.12),
0 8px 8px rgba(0,0,0,0.12),
0 16px 16px rgba(0,0,0,0.12);
}
色付き影・elevation 3段(Josh Comeau)。縦=横×2 の比を全段で維持して光源を統一し、--shadow-color を背景の色相に差し替えるだけで脱彩を防ぐ。
:root { --shadow-color: 220deg 60% 50%; }
/* 低: 単層で小さく濃い */
.elev-low { box-shadow: 0.5px 1px 1px hsl(var(--shadow-color) / 0.7); }
/* 中: 3層 */
.elev-mid {
box-shadow:
1px 2px 2px hsl(var(--shadow-color) / 0.333),
2px 4px 4px hsl(var(--shadow-color) / 0.333),
3px 6px 6px hsl(var(--shadow-color) / 0.333);
}
/* 高: 5層・薄く大きく */
.elev-high {
box-shadow:
1px 2px 2px hsl(var(--shadow-color) / 0.2),
2px 4px 4px hsl(var(--shadow-color) / 0.2),
4px 8px 8px hsl(var(--shadow-color) / 0.2),
8px 16px 16px hsl(var(--shadow-color) / 0.2),
16px 32px 32px hsl(var(--shadow-color) / 0.2);
}
ダークモード:影でなく面を明るくする(Material M2準拠)。高さ=白オーバーレイの濃度で表現する。完全対応表は 0dp 0% / 1dp 5% / 2dp 7% / 3dp 8% / 4dp 9% / 6dp 11% / 8dp 12% / 12dp 14% / 16dp 15% / 24dp 16%。
.surface-dark { background: #121212; }
.elev-1dp { background-image: linear-gradient(rgba(255,255,255,0.05),rgba(255,255,255,0.05)); } /* card */
.elev-8dp { background-image: linear-gradient(rgba(255,255,255,0.12),rgba(255,255,255,0.12)); } /* bottom app bar */
.elev-24dp { background-image: linear-gradient(rgba(255,255,255,0.16),rgba(255,255,255,0.16)); } /* dialog */
非矩形は drop-shadow、フォトリアルなカードは接触影+環境影の2枚重ね。接触影が要素を地面に固定し、環境影が柔らかいハローを作る。
/* 透過PNG/アイコンは形状に沿う drop-shadow */
.logo { filter: drop-shadow(1px 2px 4px hsl(220deg 60% 50% / 0.4)); }
/* 接触影(濃く短い) + 環境影(薄く広い) */
.product {
box-shadow:
0 1px 2px hsl(220deg 60% 50% / 0.4), /* contact */
0 12px 24px hsl(220deg 60% 50% / 0.15); /* ambient */
}
チェックリスト
- 影は単一でなく、ブラー&オフセットを倍々にした5〜6層のレイヤードにしたか
- 影色を純黒・透明黒でなく、背景の色相を借りた色(彩度中・明度低)にしたか
- ページ全体で光源を1つに固定し、全要素で縦オフセット=横オフセット×2の比を共有しているか
- elevation は offset↑・blur↑・opacity↓ を同時に動かし、low / mid / high の3段でトークン化したか
- フォトリアルなカードは接触影(濃く短い)+環境影(薄く広い)の2枚を重ねたか
- ダークモードでライト用の影トークンを流用せず、白/トーナルの面オーバーレイで高さを出したか
- 透過PNG・SVGアイコン・切り抜き画像には box-shadow でなく filter: drop-shadow() を使ったか
- モバイルで層数(6層超)やblurを使いすぎて描画コストが膨らんでいないか確認したか
限界 / 出典
数値はあくまで「出発点」。レイヤードの倍々(1→2→4→8→16px)・opacityランプ・縦=横×2比・elevation 3段の hsl 値は Tobias Ahlin と Josh Comeau の記事から取得した信頼度の高い値だが、実際の背景色・要素サイズ・余白に応じて opacity と色相の微調整は必須。Comeau の青(hue 220)は例示なので、必ず自分の背景の実色相に差し替えること。Material のダーク白オーバーレイ完全対応表(0dp〜24dp)は M2公式+二次ソースで裏取り済みだが二次ソースは blog 扱い、key umbra 0.20 / penumbra 0.14 / ambient 0.12 のトークン値も MDL 由来で一次ではない。M2(1〜24dp)と M3(0〜5レベル・トーナル優先)はバージョンで体系が異なるため、どちらに準拠するか明示が必要。hsl(220 60% 50% / .3) のスペース区切り構文はモダンブラウザ前提で、古い環境にはカンマ構文/rgba のフォールバックを。影の重ねすぎ(6層超)やblur多用はモバイルで描画コストが増えるため、バナー・広告など軽量が要る場面では層数を抑える。
primarySmoother & sharper shadows with layered box-shadows | Tobias Ahlin
primaryDesigning Beautiful Shadows in CSS — Josh W. Comeau
blogDesigning Beautiful Shadows in CSS | CSS-Tricks
primaryLight and shadows — Material Design (M2)
primaryDark theme — Material Design (M2)
primaryElevation — Material Design 3
primaryApplying elevation — Material Design 3
primaryIntroducing Tone-based Surfaces in Material 3
blogElevation (Shadows) in Material Design — CodePen (MDL values)
blogDesign for the Dark Theme — Snapp Mobile (Medium)
blogThe Anatomy of a Good Box Shadow (and Why Most Look Fake) — DEV