デザイン

ニューモーフィズムのCSS作り方|凸凹の実装と線引き

この記事の対象: ニューモーフィズムを案件で使うか判断したいWeb制作者
読了時間: 約9分

ニューモーフィズムは「背景と同じ色の面を浮かせたり沈ませたりして、要素の状態を色ではなく光で示す」ための様式です。色数を増やさずに階層を作れるので、1色で構成したダッシュボードや、トーンを揃えたいブランドサイトの装飾面では効きます。一方で、コントラスト差が影の数ピクセルしかないため、ボタンやフォームのような「操作する要素」に当てると、押せることが伝わらないまま離脱を招きます。この記事では凸と凹のCSSをそのまま動く形で示し、どこまでなら使ってよいかの線を引きます。


ニューモーフィズムとは何か|影2枚で面を起こす仕組み

ニューモーフィズムとは何か|影2枚で面を起こす仕組み

影を2枚使う理由

通常のカードUIは「白い面 + グレーの背景 + 下方向の影」で浮いて見せます。ニューモーフィズムは面と背景を同じ色にするので、この手が使えません。代わりに光源を1つ仮定し、光が当たる側に明るい影、反対側に暗い影を置きます。人間の目は明暗の組み合わせから勝手に立体を読むので、色が同じでも面が起き上がります。

光源は左上に固定するのが扱いやすいです。左上から光が来るなら、左上に白い影、右下に暗い影。この順序を途中で入れ替えると、同じページの中で凸と凹が混ざって読めなくなります。

凸(浮いている)と凹(沈んでいる)の違い

違いは inset キーワード1つだけです。

  • 凸:影を要素の外側に落とす(box-shadow に inset を付けない)
  • 凹:影を要素の内側に落とす(inset を付ける。明暗の向きも入れ替える)

凹は「くぼみ」なので、光源側(左上)に暗い影、反対側(右下)に明るい影が入ります。ここを間違えると、押し込まれているはずの面がうっすら浮いて見えて、何が選択中なのか分からなくなります。

凸と凹で影の向きが反転する

凸(浮く)

  • 左上に明るい影(外側)
  • 右下に暗い影(外側)
  • 既定状態・装飾の面に使う

凹(沈む)

  • 左上に暗い影(内側)
  • 右下に明るい影(内側)
  • 選択中・入力欄の内側に使う

光源は左上に固定し、ページ全体で向きを揃える。

凸と凹は「浮いている/沈んでいる」の対比なので、1ページの中で混在させるなら役割を決めてから使います。既定が凸、選択中が凹、という組み合わせが最も読まれやすい形です。

背景色は純白を避ける

ニューモーフィズムは明るい影を使うため、背景が #ffffff だと明るい影が出せません。白より明るい色が無いからです。実装では #e8ecf1 〜 #f0f0f3 あたりのわずかに沈んだ色を基準色にします。この基準色が決まらないと影の2色も決まらないので、最初に決めるのは影ではなく背景です。

白を主役にしたサイトの作り方は、様式としてこちらで整理しています。


ニューモーフィズムのCSS作り方|そのままコピーして動くコード

ニューモーフィズムのCSS作り方|そのままコピーして動くコード

基準色から影の2色を作る

影の色を手で決めると、背景色を変えたときに全部直すことになります。カスタムプロパティで基準色から導出しておくと、1行で色調を変えられます。

:root {
  /* 基準色。ここだけ変えれば全体の色調が動く */
  --nm-bg: #e8ecf1;

  /* 影の2色。基準色より明るい側と暗い側 */
  --nm-light: #ffffff;
  --nm-dark: #c3c9d1;

  /* 影の距離とぼかし。面の大きさに合わせて変える */
  --nm-dist: 6px;
  --nm-blur: 12px;

  /* 文字色。影ではなくここでコントラストを確保する */
  --nm-text: #2b3038;
  --nm-text-sub: #555c66;
}

body {
  margin: 0;
  padding: 48px 24px;
  background: var(--nm-bg);
  color: var(--nm-text);
  font-family: system-ui, -apple-system, "Hiragino Sans", "Noto Sans JP", sans-serif;
  line-height: 1.8;
}

--nm-light と --nm-dark は基準色から明度を数%ずらした値です。color-mix() で機械的に作る書き方もありますが、対応状況の確認が増えるので、まずは固定値で置いて後から差し替えるのが安全です。

凸(浮いている面)

.nm-raised {
  background: var(--nm-bg);
  border-radius: 20px;
  padding: 24px 28px;
  box-shadow:
    calc(var(--nm-dist) * -1) calc(var(--nm-dist) * -1) var(--nm-blur) var(--nm-light),
    var(--nm-dist) var(--nm-dist) var(--nm-blur) var(--nm-dark);
}

calc() で負の値を作っているので、--nm-dist を変えるだけで影の距離が両側そろって動きます。角丸は影より大きめに取ります。border-radius が小さいと影が角で折れて、立体ではなく「ずれた四角」に見えます。目安として、距離の3倍以上は確保しておくと破綻しません。

凹(沈んでいる面)

.nm-inset {
  background: var(--nm-bg);
  border-radius: 20px;
  padding: 24px 28px;
  box-shadow:
    inset calc(var(--nm-dist) * -1) calc(var(--nm-dist) * -1) var(--nm-blur) var(--nm-light),
    inset var(--nm-dist) var(--nm-dist) var(--nm-blur) var(--nm-dark);
}

凸と見比べると、inset が2つ付いただけです。ここで影の順序は入れ替えません。inset を付けると影が内側に落ちるため、同じ指定で自動的に明暗が反転して見えます。手で明暗を入れ替えると二重に反転して、凸と区別がつかなくなります。

凹を入力欄に使う(これは有効な使い方)

入力欄は「文字を入れる窪み」なので、凹の意味とデザインが一致します。ニューモーフィズムの中で最も破綻しにくい用途です。

.nm-field {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 14px 18px;
  border: 0;
  border-radius: 14px;
  background: var(--nm-bg);
  color: var(--nm-text);
  font-size: 16px;
  font-family: inherit;
  box-shadow:
    inset 4px 4px 8px var(--nm-dark),
    inset -4px -4px 8px var(--nm-light);
}

.nm-field::placeholder {
  color: var(--nm-text-sub);
}

/* フォーカスリングは消さない。影の上に実線で重ねる */
.nm-field:focus-visible {
  outline: 3px solid #1f5fd0;
  outline-offset: 2px;
}

font-size: 16px を明示しているのは、iOS Safari が16px未満の入力欄でフォーカス時に自動拡大する挙動を避けるためです。ニューモーフィズムは余白を広く取る様式なので、入力欄を小さく組みたくなりますが、文字サイズだけは落とさないでおきます。

ボタンに使うなら、影だけに頼らない

ボタンを凸にした実装例はよく見かけますが、影だけで押せることを示すのは成立しません。輪郭と文字のコントラストで「操作できる」ことを別に担保します。

.nm-button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 14px 28px;
  border: 1px solid var(--nm-dark);   /* 輪郭を1本足して境界を出す */
  border-radius: 14px;
  background: var(--nm-bg);
  color: var(--nm-text);
  font-size: 16px;
  font-weight: 600;                    /* 文字を太らせて操作対象だと示す */
  font-family: inherit;
  cursor: pointer;
  box-shadow:
    -5px -5px 10px var(--nm-light),
     5px  5px 10px var(--nm-dark);
  transition: box-shadow 0.18s ease;
}

/* 押したら凹む。状態変化を影で示すのはニューモーフィズムの得意分野 */
.nm-button:active,
.nm-button[aria-pressed="true"] {
  box-shadow:
    inset -5px -5px 10px var(--nm-light),
    inset  5px  5px 10px var(--nm-dark);
}

.nm-button:focus-visible {
  outline: 3px solid #1f5fd0;
  outline-offset: 3px;
}

/* 動きを減らす設定の人には transition を外す */
@media (prefers-reduced-motion: reduce) {
  .nm-button {
    transition: none;
  }
}

:active だけでなく aria-pressed="true" でも凹ませているのは、トグルボタンに使うときに状態が影で読めるようにするためです。aria-pressed はJS側で切り替えます。

<button type="button" class="nm-button" aria-pressed="false">
  通知を受け取る
</button>
document.querySelectorAll('.nm-button[aria-pressed]').forEach((btn) => {
  btn.addEventListener('click', () => {
    const on = btn.getAttribute('aria-pressed') === 'true';
    btn.setAttribute('aria-pressed', String(!on));
  });
});

影の変化は視覚的な情報なので、スクリーンリーダーには aria-pressed が届きます。見た目の凹みだけで状態を表すと、読み上げ環境では何も変わっていないことになります。ここは必ず属性とセットにします。


読みにくさとアクセシビリティ|どこで破綻するか

読みにくさとアクセシビリティ|どこで破綻するか

コントラストは影では稼げない

ニューモーフィズムの弱点は、構造的なものです。背景と面が同じ色なので、面と背景のコントラスト比は 1:1 に近い。影のぼかし部分で明度差が出るだけで、WCAG が非テキストコントラスト(画像化されたUIコンポーネントの境界など)に求める 3:1 には到底届きません。

これは実装の工夫で解決できる話ではなく、様式の定義そのものから来ています。したがって担保するのは別の場所です。

担保するもの ニューモーフィズムで使える手
文字の読みやすさ 文字色を基準色から十分離す(#2b3038 程度まで暗く)
要素の境界 border: 1px solid を1本足す
操作対象だと分かること 文字を太らせる・ラベルを動詞にする・アイコンを添える
フォーカス位置 outline を消さず、影の外側にオフセットして出す
状態(選択中・押下) 影に加えて aria-pressed / aria-current を付ける

絶対に外さないもの:フォーカスリング

ニューモーフィズムの実装例で最も頻繁に壊れているのが outline: none です。影で縁取りを作っているので outline が邪魔に見えますが、消すとキーボード操作で現在位置が分からなくなります。影は明度差がわずかしかないため、「フォーカス時だけ影を強くする」では代替になりません。

:focus-visible を使えばマウス操作では出ないので、見た目を気にする必要もありません。上のコードで outline-offset を影の距離より大きく取っているのは、ぼかしに溶けて見えなくなるのを防ぐためです。:focus-visible は一部の古いブラウザでは未対応なので、対応ブラウザ表によっては :focus のフォールバックも併記します。

動きを減らす設定への対応

ニューモーフィズムは状態変化を影のアニメーションで見せる様式なので、transition が増えがちです。影の transition は画面全体を再描画させることがあるため、prefers-reduced-motion の考慮は「配慮」ではなく実務上の必要です。

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

全体に当てる形は乱暴ですが、ニューモーフィズムのように影の遷移が大量に入るページでは、個別指定の漏れを防げます。0.01ms にしているのは、transitionend を前提に書いたJSを壊さないためです。none にするとイベントが飛ばず、処理が止まることがあります。

ダークモードとの組み合わせも相性が難しい部分です。明るい影を白のままにすると浮きすぎるので、暗い基準色に合わせて --nm-light も沈ませます。切り替えの実装自体はこちらで扱っています。


表示速度への影響|box-shadow は無料ではない

表示速度への影響|box-shadow は無料ではない

ぼかしの範囲が描画コストになる

box-shadow のぼかし半径は、そのまま描画面積に跳ね返ります。ニューモーフィズムは影を2枚重ねるので、1要素あたりのぼかし処理が単純計算で2倍です。カードを並べる枚数が増えれば、その分だけぼかしの描画も増えます。

実務で効くのは次の3点です。

  • ぼかし半径を必要以上に大きくしない。--nm-blur の値を極端に上げると、見た目の差は小さいのに描画負荷だけ上がります
  • スクロールに連動して影を変えない。スクロールごとに box-shadow を書き換えると、合成だけで済まない再描画が毎フレーム走ります
  • transition の対象を絞る。transition: all は影以外の変化も監視します。box-shadow だけに限定します

リスト全体をニューモーフィズムにしない

一覧の各行を凸にすると、影の枚数が行数×2になります。長い一覧では、行を平面に戻し、外側のコンテナだけを凹にするのが現実的です。影は2枚で済み、「この一覧はひとまとまり」という情報も伝わります。

.nm-list {
  box-shadow:
    inset 5px 5px 10px var(--nm-dark),
    inset -5px -5px 10px var(--nm-light);
  border-radius: 18px;
  padding: 8px;
}

.nm-list > li {
  box-shadow: none;              /* 行は平面に戻す */
  border-bottom: 1px solid var(--nm-dark);
  padding: 14px 16px;
  list-style: none;
}

.nm-list > li:last-child {
  border-bottom: 0;
}

ブラウザ対応の確認方法

box-shadow と inset は十分に古い機能で、現行ブラウザで問題になることはありません。注意が必要なのは組み合わせる側です。

機能 条件
box-shadow / inset 現行の主要ブラウザで利用可。IEを対象外にできるなら考えなくてよい
カスタムプロパティ(--*) 同じく広く使えるが、calc() との併用は実機で1度は目視する
:focus-visible 一部の古いブラウザで非対応。対応ブラウザ表に含めるなら :focus のフォールバックを併記する
color-mix() 対応が比較的新しい。使うなら Can I use と MDN の Browser compatibility を当日の状況で確認する

「どのバージョンから使えるか」は時期で動きます。実装前に Can I use と MDN の Browser compatibility を1回引き、案件の対応ブラウザ表と突き合わせてから決めてください。記憶や過去案件の設定を流用しないのが安全です。


案件の種類ごとの判断|使う・使わない

案件の種類ごとの判断|使う・使わない

「流行っているから」は採用理由になりません。ニューモーフィズムは色数を増やさずに階層を作る手なので、色で情報を伝える必要が薄いサイトでだけ有効です。種類ごとに言い切ります。

採用判断の順番
操作か装飾か操作要素なら影以外で境界を作る
色が情報か色に意味があるなら採用しない
対象利用者高齢者・弱視を含むなら不採用
面の数影2枚×要素数で描画コストを見る

4つのうち1つでも引っかかったら、その面はニューモーフィズムにしません。全体を諦める必要はなく、面ごとに判定します。

使ってよい案件

  • オーディオ機器・時計・家電などのプロダクトサイト:物理的なノブやボタンを連想させる様式なので、商品の文脈と一致します。ただしカートボタンは通常のベタ塗りにします
  • 社内向けダッシュボードの「数値を載せるパネル」:数値そのものはコントラストで読ませ、面の区切りだけ凹で示す形。操作系はニューモーフィズムの外に出します
  • デザイン事務所・制作会社の自社サイト:様式の選択自体が意思表示になる業種です。この業種の傾向はデザイン事務所・制作会社のWebサイトデザイン傾向|掲載12件を分類で分類しています

使わない案件

  • 金融・証券・保険:数字の誤読が直接損害になります。入力欄の境界が曖昧な金額フォームは論外です。この領域の実例傾向は金融・証券・保険Webサイトデザイン事例|掲載6件を分類してわかったことで整理しています
  • 病院・医療:利用者に高齢者と弱視の方が含まれる前提で設計する領域です。コントラストを落とす様式は選択肢に入りません
  • EC(特にカート・決済導線):購入ボタンが押せると分からないのは、デザインの問題ではなく売上の問題です。装飾の面に限定するなら可
  • 採用サイト:スマホの屋外閲覧が多く、画面が見づらい環境で読まれます。応募フォームは特に避けます

装飾だけに使う現実的な構成

実務で最も採用しやすいのは「装飾の面だけニューモーフィズム、操作系は通常のUI」という混在です。様式の雰囲気は出せて、コントラストの問題は操作要素に触れません。

要素 扱い
セクションの背景パネル 凹。情報のまとまりを示す
数値・実績を載せるカード 凸。文字色でコントラストを確保
アイコンの丸い台座 凸。アイコン自体は濃色
ボタン・リンク 通常のUI。ベタ塗り + 十分なコントラスト
入力欄 凹は可。ただしフォーカスリングは実線で
エラー表示 通常のUI。色と文言の両方で示す

この分け方なら、1カラムの縦長構成とも噛み合います。縦に積む構成そのものの作り方は1カラムWebデザインレイアウトの実例8選|向いているケースと作り方で扱っています。


よくある質問

よくある質問

ニューモーフィズムはもう古いのでしょうか

様式としての新鮮さは落ちていますが、「色を増やさずに面の階層を作る」という機能は古びていません。判断基準は流行ではなく、そのサイトで色が情報を担っているかどうかです。色に意味を持たせる必要があるサイトでは、流行していた時期でも適しませんでした。

影だけでボタンを表現してはいけない理由は何ですか

面と背景が同じ色なので、コントラスト比が 1:1 に近く、操作できる要素に必要な視認性を満たせないためです。押せると分かるかどうかは、輪郭線・文字の太さ・ラベルの文言で担保します。影は状態変化(押された・選択中)を示す補助として使うのが適切です。

背景が白いサイトでニューモーフィズムは使えますか

そのままでは使えません。明るい側の影に白より明るい色が必要になるためです。基準色を #e8ecf1 程度まで沈ませるか、ニューモーフィズムを使う区画だけ背景色を変えて、そこで完結させてください。

ダークモードでも同じCSSで動きますか

カスタムプロパティを基準色から組んでいれば、--nm-bg --nm-light --nm-dark の3つを差し替えるだけで動きます。ただし暗い背景では明るい影を白のままにすると浮きすぎるので、--nm-light も基準色寄りに沈ませます。文字色のコントラストは明暗それぞれで測り直してください。

影を2枚重ねると表示速度に影響しますか

ぼかし半径と要素数の掛け算で効いてきます。一覧の各行を凸にすると影の枚数が行数×2になるので、行は平面に戻してコンテナだけ凹にします。ぼかしを必要以上に大きくしないこと、スクロール連動で box-shadow を書き換えないこと、transition: all を避けることの3点で、実務上の問題はほぼ回避できます。


まとめ

ニューモーフィズムは、背景と同じ色の面を明暗2枚の box-shadow で起こす様式です。実装は inset の有無で凸と凹を切り替えるだけで、カスタムプロパティに基準色と距離を持たせれば色調も一括で動きます。

ただしコントラストが構造的に足りないため、操作する要素には使わないのが線引きです。ボタンは輪郭線と文字の太さで境界を作り、フォーカスリングは消さず、状態は aria-pressed などの属性と必ずセットにします。装飾の面だけに限定すれば、様式の雰囲気は保ったまま読みやすさを落とさずに組めます。

採用の判断は4つの問いで足ります。操作か装飾か、色が情報を担っているか、対象利用者に見づらい環境の人を含むか、面の数が描画コストに見合うか。1つでも引っかかる面には使わない。これだけ決めておけば、案件ごとに迷う時間が消えます。