box-shadow は要素のボーダーボックスに影を付けるプロパティです。定義は CSS Backgrounds and Borders Module Level 3 の 6.1 節にあり、「色は省略できるか」「ぼかしの値は何ピクセル広がるのか」「角丸のとき影の角はどうなるか」といった疑問の答えもそこに書かれています。この記事では仕様の該当箇所を読みながら、Chrome 152(macOS、2026 年 10 月 2 日)でテスト用の要素に宣言を当て、getComputedStyle() の値とスクリーンショットの画素を読んで確かめた結果を示します。
box-shadowの書き方:値の順番と省略できるもの
仕様の文法は次のとおりです。
<shadow> = <color>? && [ <length>{2} <length [0,∞]>? <length>? ] && inset?
&& は 3 つのまとまりを好きな順に書けるという意味です。ただし長さの値(2〜4 個)は続けて、決まった順に書きます。影を複数付けるときはカンマで区切ります。
| 位置 | 必須 | 意味 | 省略したとき |
|---|---|---|---|
| 1 番目の長さ | 必須 | 水平方向のずれ。正の値で右へ | — |
| 2 番目の長さ | 必須 | 垂直方向のずれ。正の値で下へ | — |
| 3 番目の長さ | 任意 | ぼかし半径。負の値は無効 | 0(くっきりした影) |
| 4 番目の長さ | 任意 | 広がり(spread)。負の値で縮む | 0 |
| 色 | 任意 | 影の色 | currentColor(文字色) |
inset | 任意 | 内側の影にする | 外側の影 |
文字色が赤、font-size が 20px の要素に当てたときの Chrome の計算値は次のとおりでした。
| 宣言 | 計算値(getComputedStyle) |
|---|---|
4px 4px | rgb(255, 0, 0) 4px 4px 0px 0px |
inset 2px 2px red | rgb(255, 0, 0) 2px 2px 0px 0px inset |
1em 0 red | rgb(255, 0, 0) 20px 0px 0px 0px |
0 0 -5px red | none(宣言ごと無視) |
2px red 2px | none(長さの間に色) |
1px 2px 3px 4px 5px red | none(長さが 5 個) |
色を省略すると文字色になるので、リンクに付けた影が青くなる、という現象はこれで説明できます。em は px に換算されるため、文字サイズに合わせて影も大きくなります。書き間違えた宣言は既定値に置き換わるのではなく丸ごと無視され、それ以前に指定されていた値が残ります。
ぼかし(blur)の値が描く範囲
仕様は、ぼかし半径でちょうど影が終わるような描き方を求めていません。求めているのは「標準偏差がぼかし半径の半分のガウスぼかし」に近い結果で、まっすぐな辺では影の端を中心に、ぼかし半径のおよそ 2 倍の幅で色が変化すると注記しています。つまり 10px のぼかしは、影の端から内側に約 10px、外側に約 10px の範囲で薄くなります。
黒い 100 × 100 の箱に box-shadow: 0 0 10px #000 を付け、白背景の上で箱の右辺から外側の画素の濃さを測ると次のとおりでした。
| 辺からの距離 | 0px | 2px | 5px | 8px | 10px | 15px |
|---|---|---|---|---|---|---|
| 黒の割合 | 0.459 | 0.310 | 0.137 | 0.047 | 0.016 | 0 |
標準偏差 5px のガウス分布から計算した値(0.5、0.34、0.16、0.05、0.02、0.001)とほぼ一致します。ずれも spread も 0 なのに影が箱より大きく見えるのは、ぼかしの帯の半分が箱の外側にあるためです。
下だけ・上だけ・片側だけに影を付ける
「下だけに影を付けたい」という要望には、spread を負の値にする方法がよく使われます。
.header {
box-shadow: 0 10px 10px -10px rgba(0, 0, 0, 0.5);
}
spread の -10px で影の四辺をすべて 10px 内側に縮めると、ぼかし 10px は左右と上の辺の外側に届かなくなります。そこへ垂直方向のずれ 10px を加えると、下辺の外側にだけぼかしが現れます。灰色の箱にこの影(色は黒)を付けて測ると、箱の左・右・上 3px の画素は 253〜255(ほぼ白)で、下 2px は 176、下 6px は 230 でした。同じ考え方で向きを変えられます。
| 影を付けたい辺 | 宣言の例 |
|---|---|
| 下だけ | 0 10px 10px -10px |
| 上だけ | 0 -10px 10px -10px |
| 右だけ | 10px 0 10px -10px |
| 左右だけ | 10px 0 10px -10px, -10px 0 10px -10px |
上だけの影では上 3px が 176、左右と下は 253〜255、左右だけの影では左右が 176〜194、上下は 251〜253 で、いずれも狙った辺にだけ影が出ました。ぼかしと spread の絶対値をそろえるのがこつです。
spread と border-radius:角丸の影
spread は影の辺を外側(負なら内側)へ動かします。形を保つため、仕様は影の角の半径にも spread を足します。ただし角丸の半径が spread より小さいときは、足す量に 1 + (r − 1)³(r は半径 ÷ spread)を掛けて小さくします。仕様の例では、半径 10px・spread 20px の影の角は 30px ではなく 27.5px になります。角が直角の箱では影の角も直角のまま、少しだけ丸い箱では影も少しだけ丸くなるための調整です。
100 × 100 の箱に box-shadow: 0 0 0 40px #000 を付け、箱の左上の角から斜め 45 度方向に黒が続く距離を測りました。
| border-radius | 仕様上の影の角の半径 | 斜め方向に黒が続いた距離 |
|---|---|---|
| 0 | 0 | 40px(直角) |
| 10px | 33.125px | 30px |
| 60px(100px の箱では 50px に縮む) | 90px | 14px(円) |
調整がなければ 10px の場合は半径 50px になり、25px で止まるはずです。円形のアバター画像に spread で輪郭線を付けると、影もきちんと円になるのはこの規則のおかげです。
内側の影(inset)
inset を付けると、仕様の言葉では「箱をキャンバスから切り抜いて、その後ろにずらしたような」影になります。内側の影はパディングの縁より内側にだけ描かれ、背景より上、ボーダーより下に重なります。spread の向きも逆で、正の値を指定すると影の形が縮んで影の面積が増えます。灰色の箱に box-shadow: inset 0 0 0 10px #000 を付けると、縁から 1〜9px の画素は黒、10px から内側は背景の灰色のままで、幅 10px の帯になりました。
この帯はレイアウトを動かさない枠線として使えます。押し込まれたボタンや入力欄のくぼみ(inset 0 2px 4px rgba(0, 0, 0, 0.15))、フォーカス時のリングも同じ仕組みです。
影を重ねる:デジタル庁デザインシステムのエレベーション
カンマで区切った影は前から順に描かれ、仕様は「最初の影がいちばん上」と定めています。デジタル庁が MIT ライセンスで公開しているデザイントークン(npm の @digital-go-jp/design-tokens 2.0.1、dist/tokens.css)では、浮き上がりの段階(エレベーション)を 2 枚の影で表しています。
--elevation-1: 0 2px 8px 1px rgba(0,0,0,0.1), 0 1px 5px 0 rgba(0,0,0,0.3);
--elevation-4: 0 6px 20px 4px rgba(0,0,0,0.1), 0 2px 6px 0 rgba(0,0,0,0.3);
--elevation-8: 0 14px 40px 7px rgba(0,0,0,0.1), 0 3px 16px 0 rgba(0,0,0,0.3);
1 枚目は広く薄い影、2 枚目は狭く濃い影です。段階が上がるほど 1 枚目のずれ・ぼかし・spread が大きくなり、2 枚目はあまり変わりません。灰色の箱に --elevation-1 を付けて測ると、箱の外 3px は上 244、左 234、下 232 で、四方に影がありつつ下側が少し濃くなっていました。
ボックスシャドウジェネレーターは 1 枚ずつ影を作るので、重ねる場合は 1 枚ごとに作ってカンマでつなぎます。--elevation-1 なら、1 枚目は水平 0、垂直 2、ぼかし 8、spread 1、不透明度 10%、2 枚目は 0、1、5、0、30% に設定します。
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.10);
box-shadow: 0px 1px 5px 0px rgba(0, 0, 0, 0.30);
/* 2 枚をつなげたもの */
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.10), 0px 1px 5px 0px rgba(0, 0, 0, 0.30);
ジェネレーターは長さを 4 つとも px で書き、色を小数 2 桁の rgba() で出力します。先に挙げた下だけの影も、垂直 10、ぼかし 10、spread −10、不透明度 50% で box-shadow: 0px 10px 10px -10px rgba(0, 0, 0, 0.50); になります。スライダーの範囲はずれと spread が −50〜50px、ぼかしが 0〜100px、不透明度は 1% 刻みです。それ以外の値や単位は出力後に手で直してください。
filter: drop-shadow() との違い
見た目は似ていますが、影の元が違います。box-shadow は「ボーダーボックスが不透明であるかのように」影を落とし、箱の内側では影を切り取ります。Filter Effects Level 1 の filter: drop-shadow() は、要素と子孫が実際に描いた画素の不透明部分(アルファ)に影を付けます。
box-shadow | filter: drop-shadow() | |
|---|---|---|
| 影の形 | ボーダーボックス(角丸を含む) | 描画結果のアルファ(文字や透過 PNG の形) |
| spread | あり | なし(Chrome 152 は drop-shadow(0 0 4px 2px red) を無効として無視) |
| inset | あり | なし |
| 3 番目の長さ | ぼかし半径(標準偏差はその半分) | 標準偏差そのもの |
| 副作用 | レイアウトや重なり順に影響しない | 重ね合わせコンテキストと、固定・絶対配置の子孫の包含ブロックを作る |
3 番目の長さの違いは見落としやすい点です。同じ 10px でも drop-shadow はぼかしが約 2 倍広がります。上と同じ黒い箱で測ると、filter: drop-shadow(0 0 10px #000) は辺から 5px で 0.294、10px で 0.149 で、0 になったのは 30px でした(box-shadow のぼかし 10px は 0.137 と 0.016)。box-shadow のぼかし 10px に合わせるなら drop-shadow(0 0 5px …) と書きます。
切り取りの違いも確かめました。背景が透明な箱に box-shadow: 10px 10px 0 #f00 を付けると、箱と影が重なる部分は白のままで、赤いのは箱の外にはみ出した部分だけでした。カードやボタンには box-shadow、アイコンや切り抜き画像、四角以外の形には drop-shadow() と使い分けます。
パフォーマンスと強制カラーモード
仕様は影が「レイアウトに影響しない」「スクロールを発生させず、スクロール可能なオーバーフロー領域を広げない」と定めています。Chrome 152 でも、幅 100% の要素に水平 300px の影を付けてもページの scrollWidth は変わりませんでした。
一方、影のアニメーションには描き直しのコストがかかります。60 フレームにわたって box-shadow を変え続けたところ、Chrome のパフォーマンスカウンター(Performance.getMetrics)ではスタイル再計算が 59〜60 回、レイアウトは 0〜1 回でした(3 回測定)。同じように width を変えるとレイアウトが毎回 59 回です。レイアウトは起きませんが、要素はフレームごとに再描画されます。web.dev の アニメーションのガイド はアニメーションには transform と opacity だけを使うよう勧めています。ホバーで影を濃くしたいときは、最終的な影を疑似要素に付けて opacity を変えます。
.card {
position: relative;
}
.card::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}
.card:hover::after {
opacity: 1;
}
Windows のハイコントラストなどの強制カラーモードでは、box-shadow は none に強制されます(CSS Color Adjust Level 1 3.1 節)。影だけで描いたフォーカスリングは消えてしまうので、outline: 2px solid transparent を併記しておくと、強制カラーモードではシステムの色で枠が描かれます。
.button:focus-visible {
outline: 2px solid transparent;
box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.5);
}