ニューモーフィックボタン

背景と同色+二方向シャドウで浮き沈みするニューモーフィズムのボタン群。押すと凹む立体感で、トグルやアイコン操作に向きます。

#css#neumorphism#button#javascript

ライブデモ

使用例(お題: SaaS FlowDesk)

この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- FlowDesk ヒーロー下:ニューモーフィックなボタン群 -->
<section class="fb-hero">
  <p class="fb-hero__eyebrow">FLOWDESK</p>
  <h1 class="fb-hero__title">チームの業務を、ひとつに。</h1>
  <p class="fb-hero__lead">タスク・ファイル・コミュニケーションを集約する業務効率化プラットフォーム。</p>

  <!-- メインCTA群 -->
  <div class="fb-cta">
    <button class="fb-btn fb-btn--primary" type="button">無料で始める</button>
    <button class="fb-btn" type="button">▷ デモを見る</button>
  </div>

  <!-- 表示切替トグル(押し込みで凹む) -->
  <div class="fb-toggle" role="group" aria-label="表示切替">
    <button class="fb-toggle__item is-active" type="button" data-view="board">ボード</button>
    <button class="fb-toggle__item" type="button" data-view="list">リスト</button>
    <button class="fb-toggle__item" type="button" data-view="calendar">カレンダー</button>
  </div>

  <p class="fb-readout" id="fbReadout">現在の表示:ボード</p>
</section>
CSS
/* FlowDesk:ニューモーフィックなボタン群 */
:root {
  --bg: #e7ecf6;
  --dark: #c4cbda;
  --light: #ffffff;
  --navy: #0f1b34;
  --blue: #4f7cff;
  --text: #46506b;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 400px;
  display: grid;
  place-items: center;
  background: var(--bg);
  font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
  color: var(--text);
  overflow: hidden;
}

.fb-hero {
  width: min(480px, 92vw);
  text-align: center;
  padding: 8px;
}
.fb-hero__eyebrow {
  margin: 0;
  font-size: 11px;
  letter-spacing: 0.3em;
  color: var(--blue);
  font-weight: 700;
}
.fb-hero__title {
  margin: 6px 0 8px;
  font-size: 26px;
  font-weight: 800;
  color: var(--navy);
  letter-spacing: 0.01em;
}
.fb-hero__lead {
  margin: 0 0 20px;
  font-size: 13px;
  line-height: 1.7;
  color: #687293;
}

/* メインCTA */
.fb-cta {
  display: flex;
  gap: 14px;
  justify-content: center;
  margin-bottom: 22px;
}

/* 凸ボタンの基本形 */
.fb-btn {
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  padding: 12px 24px;
  border: none;
  border-radius: 14px;
  cursor: pointer;
  color: var(--navy);
  background: var(--bg);
  box-shadow: 6px 6px 12px var(--dark), -6px -6px 12px var(--light);
  transition: box-shadow 0.18s ease, transform 0.18s ease, color 0.18s ease;
}
.fb-btn:hover { transform: translateY(-1px); }
/* 押すと凹む */
.fb-btn:active {
  box-shadow: inset 4px 4px 8px var(--dark), inset -4px -4px 8px var(--light);
  transform: translateY(0);
}

.fb-btn--primary {
  color: #fff;
  background: linear-gradient(135deg, #5f8bff, #4f7cff);
  box-shadow: 6px 6px 14px rgba(79,124,255,0.45), -6px -6px 12px var(--light);
}
.fb-btn--primary:active { box-shadow: inset 4px 4px 9px rgba(35,70,170,0.55); }

/* セグメントトグル:凹みトラックの中に並ぶボタン */
.fb-toggle {
  display: inline-flex;
  gap: 4px;
  padding: 6px;
  border-radius: 16px;
  background: var(--bg);
  box-shadow: inset 4px 4px 9px var(--dark), inset -4px -4px 9px var(--light);
}
.fb-toggle__item {
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  padding: 8px 16px;
  border: none;
  border-radius: 11px;
  cursor: pointer;
  color: #8089a3;
  background: transparent;
  transition: box-shadow 0.22s ease, color 0.22s ease;
}
/* アクティブ項目は凸で浮き出す */
.fb-toggle__item.is-active {
  color: var(--blue);
  background: var(--bg);
  box-shadow: 4px 4px 8px var(--dark), -4px -4px 8px var(--light);
}

.fb-readout {
  margin: 16px 0 0;
  font-size: 12px;
  color: #8a93ab;
  min-height: 1em;
}

@media (prefers-reduced-motion: reduce) {
  .fb-btn, .fb-toggle__item { transition: none; }
}
JavaScript
// セグメントトグルの表示切替(凸/凹の見た目はCSSで表現)
const items = document.querySelectorAll(".fb-toggle__item");
const readout = document.getElementById("fbReadout");

// data-view と日本語ラベルの対応
const labels = { board: "ボード", list: "リスト", calendar: "カレンダー" };

items.forEach((item) => {
  item.addEventListener("click", () => {
    // 既存のアクティブを解除して自分を有効化
    items.forEach((el) => el.classList.remove("is-active"));
    item.classList.add("is-active");
    const view = item.dataset.view || "board";
    if (readout) readout.textContent = `現在の表示:${labels[view] ?? view}`;
  });
});

// メインCTAの軽いフィードバック
const primary = document.querySelector(".fb-btn--primary");
primary?.addEventListener("click", () => {
  const label = primary.textContent;
  primary.textContent = "ようこそ FlowDesk へ!";
  setTimeout(() => { primary.textContent = label; }, 1500);
});

実装ガイド

使いどころ

設定パネルのアイコントグル、電卓やメディアプレーヤーの操作ボタン、ミュート/再生などの ON/OFF を伴うコントロール群に向きます。やわらかいトーンで統一したいダッシュボードのアクション類に最適です。

実装時の注意点

ニューモーフィズムの肝は『ボタンと背景を同色にする』ことと、明(左上)と暗(右下)の二方向 box-shadow で擬似的な光源を作る点です。押下状態は同じ影を inset に反転させるだけで凹みを表現でき、JS は aria-pressed の真偽を切り替えるのみ(見た目はCSSの属性セレクタが担当)でアクセシブルに状態を伝えられます。背景色・明暗の影色は3つのカスタムプロパティで一元管理すると破綻しません。

対応ブラウザ

実体は box-shadow(複数指定・inset 含む)だけのため、IE 含む全モダンブラウザで安定動作します。backdrop-filter のようなプレフィックスや対応バージョンの心配は不要で、フォールバックも基本的に要りません。

よくある失敗

影と背景の明度差が小さいため、ボタンの輪郭が曖昧になり押せる要素だと気づかれにくいのが最大の弱点です。文字や枠のコントラストが WCAG 基準(テキスト4.5:1、UIコンポーネント1.4.11の3:1)を満たしにくく、純白すぎる/暗すぎる背景では二方向シャドウのどちらかが消えて立体感が失われます。影を濃く・大きくしすぎると古い『押し込みボタン』的な野暮ったさが出ます。

応用例

:focus-visible で明確なフォーカスリングを足してキーボード操作を補助する、accent 色をホバー/押下時の文字色に使って状態を色でも伝える、prefers-color-scheme でダーク版(暗い背景+暗側を黒寄り・明側をグレー寄りに)を用意する、といった拡張でアクセシビリティと適用範囲を広げられます。

コード

HTML
<!-- ニューモーフィックなトグル群(押すと凹む立体ボタン) -->
<div class="neu-stage">
  <p class="neu-stage__label">Neumorphic Buttons</p>

  <div class="neu-row">
    <!-- アイコンボタン3種:クリックでON/OFF(押し込み表現) -->
    <button class="neu-btn" type="button" data-label="再生" aria-pressed="false">
      <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg>
    </button>
    <button class="neu-btn" type="button" data-label="お気に入り" aria-pressed="false">
      <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s-7.5-4.6-10-9.2C.2 8.1 2.1 5 5.4 5 7.3 5 8.7 6 12 8.5 15.3 6 16.7 5 18.6 5 21.9 5 23.8 8.1 22 11.8 19.5 16.4 12 21 12 21z"/></svg>
    </button>
    <button class="neu-btn" type="button" data-label="設定" aria-pressed="false">
      <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 8a4 4 0 100 8 4 4 0 000-8zm9 4l-2 .3a7 7 0 01-.6 1.5l1.2 1.6-1.5 1.5-1.6-1.2a7 7 0 01-1.5.6L14.7 20h-2.1l-.3-2a7 7 0 01-1.5-.6l-1.6 1.2-1.5-1.5 1.2-1.6a7 7 0 01-.6-1.5L4.3 14v-2.1l2-.3a7 7 0 01.6-1.5L5.7 8.5l1.5-1.5 1.6 1.2a7 7 0 011.5-.6L11.6 4h2.1l.3 2a7 7 0 011.5.6l1.6-1.2 1.5 1.5-1.2 1.6a7 7 0 01.6 1.5L20 11.6z"/></svg>
    </button>
  </div>

  <!-- ピル型のプライマリボタン -->
  <button class="neu-pill" type="button">押してみる</button>
  <p class="neu-status" id="neuStatus">未操作</p>
</div>
CSS
/* ニューモーフィズムは「背景と同色+二方向の影」が肝 */
:root {
  --bg: #e0e5ec;
  --shadow-dark: #a3b1c6;
  --shadow-light: #ffffff;
  --accent: #6d5dfc;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 360px;
  display: grid;
  place-items: center;
  background: var(--bg);
  font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
  color: #5b6478;
}

.neu-stage { display: flex; flex-direction: column; align-items: center; gap: 22px; }
.neu-stage__label { margin: 0; font-size: 13px; letter-spacing: 0.16em; color: #9aa6b8; text-transform: uppercase; }

.neu-row { display: flex; gap: 26px; }

/* 凸ボタン:明暗2方向の box-shadow で浮き上がらせる */
.neu-btn {
  width: 64px; height: 64px;
  display: grid; place-items: center;
  border: none; cursor: pointer;
  border-radius: 18px;
  background: var(--bg);
  color: #7a8699;
  box-shadow: 6px 6px 12px var(--shadow-dark), -6px -6px 12px var(--shadow-light);
  transition: box-shadow 0.18s ease, color 0.18s ease, transform 0.18s ease;
}
.neu-btn svg { width: 26px; height: 26px; fill: currentColor; }
.neu-btn:hover { color: var(--accent); }
.neu-btn:active { transform: scale(0.97); }

/* 押下(ON)状態:影を内側に反転して「凹み」を表現 */
.neu-btn[aria-pressed="true"] {
  color: var(--accent);
  box-shadow: inset 5px 5px 10px var(--shadow-dark), inset -5px -5px 10px var(--shadow-light);
}

/* ピルボタン:アクセント文字色の凸ボタン */
.neu-pill {
  font: inherit; font-weight: 600; font-size: 15px;
  color: var(--accent);
  padding: 14px 40px;
  border: none; cursor: pointer;
  border-radius: 999px;
  background: var(--bg);
  box-shadow: 6px 6px 14px var(--shadow-dark), -6px -6px 14px var(--shadow-light);
  transition: box-shadow 0.18s ease, transform 0.12s ease;
}
.neu-pill:hover { box-shadow: 8px 8px 16px var(--shadow-dark), -8px -8px 16px var(--shadow-light); }
.neu-pill:active {
  box-shadow: inset 4px 4px 9px var(--shadow-dark), inset -4px -4px 9px var(--shadow-light);
  transform: translateY(1px);
}

.neu-status { margin: 0; font-size: 12.5px; color: #9aa6b8; min-height: 1em; }

@media (prefers-reduced-motion: reduce) {
  .neu-btn, .neu-pill { transition: none; }
}
JavaScript
// アイコンボタンのトグルとステータス表示
const status = document.getElementById("neuStatus");
const iconButtons = document.querySelectorAll(".neu-btn");

// 各アイコンボタン:押すたびに aria-pressed を反転(凹み表現はCSS側)
iconButtons.forEach((btn) => {
  btn.addEventListener("click", () => {
    const pressed = btn.getAttribute("aria-pressed") === "true";
    btn.setAttribute("aria-pressed", String(!pressed));
    const label = btn.dataset.label ?? "ボタン";
    if (status) status.textContent = `${label} を ${!pressed ? "ON" : "OFF"} にしました`;
  });
});

// ピルボタン:押下回数をカウント
const pill = document.querySelector(".neu-pill");
let count = 0;
pill?.addEventListener("click", () => {
  count += 1;
  if (status) status.textContent = `${count} 回押しました`;
});

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「ニューモーフィックボタン」の効果を追加してください。

# 追加してほしい効果
ニューモーフィックボタン(グラス / ニューモーフィズム)
背景と同色+二方向シャドウで浮き沈みするニューモーフィズムのボタン群。押すと凹む立体感で、トグルやアイコン操作に向きます。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- ニューモーフィックなトグル群(押すと凹む立体ボタン) -->
<div class="neu-stage">
  <p class="neu-stage__label">Neumorphic Buttons</p>

  <div class="neu-row">
    <!-- アイコンボタン3種:クリックでON/OFF(押し込み表現) -->
    <button class="neu-btn" type="button" data-label="再生" aria-pressed="false">
      <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg>
    </button>
    <button class="neu-btn" type="button" data-label="お気に入り" aria-pressed="false">
      <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s-7.5-4.6-10-9.2C.2 8.1 2.1 5 5.4 5 7.3 5 8.7 6 12 8.5 15.3 6 16.7 5 18.6 5 21.9 5 23.8 8.1 22 11.8 19.5 16.4 12 21 12 21z"/></svg>
    </button>
    <button class="neu-btn" type="button" data-label="設定" aria-pressed="false">
      <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 8a4 4 0 100 8 4 4 0 000-8zm9 4l-2 .3a7 7 0 01-.6 1.5l1.2 1.6-1.5 1.5-1.6-1.2a7 7 0 01-1.5.6L14.7 20h-2.1l-.3-2a7 7 0 01-1.5-.6l-1.6 1.2-1.5-1.5 1.2-1.6a7 7 0 01-.6-1.5L4.3 14v-2.1l2-.3a7 7 0 01.6-1.5L5.7 8.5l1.5-1.5 1.6 1.2a7 7 0 011.5-.6L11.6 4h2.1l.3 2a7 7 0 011.5.6l1.6-1.2 1.5 1.5-1.2 1.6a7 7 0 01.6 1.5L20 11.6z"/></svg>
    </button>
  </div>

  <!-- ピル型のプライマリボタン -->
  <button class="neu-pill" type="button">押してみる</button>
  <p class="neu-status" id="neuStatus">未操作</p>
</div>

【CSS】
/* ニューモーフィズムは「背景と同色+二方向の影」が肝 */
:root {
  --bg: #e0e5ec;
  --shadow-dark: #a3b1c6;
  --shadow-light: #ffffff;
  --accent: #6d5dfc;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 360px;
  display: grid;
  place-items: center;
  background: var(--bg);
  font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
  color: #5b6478;
}

.neu-stage { display: flex; flex-direction: column; align-items: center; gap: 22px; }
.neu-stage__label { margin: 0; font-size: 13px; letter-spacing: 0.16em; color: #9aa6b8; text-transform: uppercase; }

.neu-row { display: flex; gap: 26px; }

/* 凸ボタン:明暗2方向の box-shadow で浮き上がらせる */
.neu-btn {
  width: 64px; height: 64px;
  display: grid; place-items: center;
  border: none; cursor: pointer;
  border-radius: 18px;
  background: var(--bg);
  color: #7a8699;
  box-shadow: 6px 6px 12px var(--shadow-dark), -6px -6px 12px var(--shadow-light);
  transition: box-shadow 0.18s ease, color 0.18s ease, transform 0.18s ease;
}
.neu-btn svg { width: 26px; height: 26px; fill: currentColor; }
.neu-btn:hover { color: var(--accent); }
.neu-btn:active { transform: scale(0.97); }

/* 押下(ON)状態:影を内側に反転して「凹み」を表現 */
.neu-btn[aria-pressed="true"] {
  color: var(--accent);
  box-shadow: inset 5px 5px 10px var(--shadow-dark), inset -5px -5px 10px var(--shadow-light);
}

/* ピルボタン:アクセント文字色の凸ボタン */
.neu-pill {
  font: inherit; font-weight: 600; font-size: 15px;
  color: var(--accent);
  padding: 14px 40px;
  border: none; cursor: pointer;
  border-radius: 999px;
  background: var(--bg);
  box-shadow: 6px 6px 14px var(--shadow-dark), -6px -6px 14px var(--shadow-light);
  transition: box-shadow 0.18s ease, transform 0.12s ease;
}
.neu-pill:hover { box-shadow: 8px 8px 16px var(--shadow-dark), -8px -8px 16px var(--shadow-light); }
.neu-pill:active {
  box-shadow: inset 4px 4px 9px var(--shadow-dark), inset -4px -4px 9px var(--shadow-light);
  transform: translateY(1px);
}

.neu-status { margin: 0; font-size: 12.5px; color: #9aa6b8; min-height: 1em; }

@media (prefers-reduced-motion: reduce) {
  .neu-btn, .neu-pill { transition: none; }
}

【JavaScript】
// アイコンボタンのトグルとステータス表示
const status = document.getElementById("neuStatus");
const iconButtons = document.querySelectorAll(".neu-btn");

// 各アイコンボタン:押すたびに aria-pressed を反転(凹み表現はCSS側)
iconButtons.forEach((btn) => {
  btn.addEventListener("click", () => {
    const pressed = btn.getAttribute("aria-pressed") === "true";
    btn.setAttribute("aria-pressed", String(!pressed));
    const label = btn.dataset.label ?? "ボタン";
    if (status) status.textContent = `${label} を ${!pressed ? "ON" : "OFF"} にしました`;
  });
});

// ピルボタン:押下回数をカウント
const pill = document.querySelector(".neu-pill");
let count = 0;
pill?.addEventListener("click", () => {
  count += 1;
  if (status) status.textContent = `${count} 回押しました`;
});

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。