下部タブバー

モバイルアプリ風の下部固定タブバー。選択したタブへ丸いインジケーターがなめらかにスライドし、アイコンが浮き上がるように強調表示されます。通知バッジ付き。

#css#js#nav#mobile

ライブデモ

コード

HTML
<!-- モバイルアプリ風の下部固定タブバー。選択タブへインジケーターがスライドし、アイコンが強調表示される -->
<div class="nbt-frame" id="nbtFrame">
  <div class="nbt-app">
    <header class="nbt-appbar"><span>ホーム</span></header>
    <div class="nbt-feed">
      <div class="nbt-post"></div>
      <div class="nbt-post"></div>
      <div class="nbt-post"></div>
    </div>
  </div>

  <nav class="nbt-tabbar" aria-label="下部ナビゲーション" id="nbtTabbar">
    <span class="nbt-indicator" id="nbtIndicator" aria-hidden="true"></span>
    <button class="nbt-tab is-active" type="button" aria-current="page" data-tab="home">
      <span class="nbt-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 11l9-7 9 7"></path><path d="M5 10v10h14V10"></path></svg></span>
      <span class="nbt-label">ホーム</span>
    </button>
    <button class="nbt-tab" type="button" data-tab="search">
      <span class="nbt-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"></circle><path d="M21 21l-4.3-4.3"></path></svg></span>
      <span class="nbt-label">検索</span>
    </button>
    <button class="nbt-tab" type="button" data-tab="post">
      <span class="nbt-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"></path></svg></span>
      <span class="nbt-label">投稿</span>
    </button>
    <button class="nbt-tab" type="button" data-tab="alerts">
      <span class="nbt-icon" aria-hidden="true">
        <svg viewBox="0 0 24 24"><path d="M6 9a6 6 0 0112 0c0 5 2 6 2 6H4s2-1 2-6"></path><path d="M10 20a2 2 0 004 0"></path></svg>
        <span class="nbt-badge">3</span>
      </span>
      <span class="nbt-label">通知</span>
    </button>
    <button class="nbt-tab" type="button" data-tab="me">
      <span class="nbt-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"></circle><path d="M4 20c1.3-4 4.4-6 8-6s6.7 2 8 6"></path></svg></span>
      <span class="nbt-label">マイページ</span>
    </button>
  </nav>
</div>
CSS
* { box-sizing: border-box; }
body { margin: 0; display: block; background: #f4f5f8; font-family: "Hiragino Sans", "Segoe UI", system-ui, sans-serif; }

/* ルート: width:100% + height:100vh でカード/モーダルどちらの枠にも隙間なく収まる */
.nbt-frame { position: relative; width: 100%; height: 100vh; overflow: hidden; background: #f4f5f8; }

.nbt-appbar { height: 52px; display: flex; align-items: center; padding: 0 18px; background: #fff; border-bottom: 1px solid #e8e9f0; font-weight: 800; font-size: 15px; color: #1c2033; }
.nbt-feed { padding: 14px; display: flex; flex-direction: column; gap: 12px; height: calc(100% - 52px - 64px); overflow: hidden; }
.nbt-post { height: 90px; border-radius: 14px; background: linear-gradient(120deg, #fff, #eceefb); border: 1px solid #e8e9f0; flex-shrink: 0; }

.nbt-tabbar {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 10;
  display: flex; height: 64px; background: #fff;
  border-top: 1px solid #e8e9f0; box-shadow: 0 -6px 20px rgba(20, 24, 50, .05);
}
.nbt-indicator {
  position: absolute; top: 8px; left: 0; height: 40px; width: 40px; border-radius: 14px;
  background: #eef1ff; transition: transform .3s cubic-bezier(.22, 1, .36, 1), width .3s ease;
  will-change: transform;
}
.nbt-tab {
  position: relative; flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  background: none; border: none; cursor: pointer; padding: 6px 2px;
  color: #9298ab; font: inherit;
}
.nbt-icon { position: relative; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; transition: transform .25s cubic-bezier(.34, 1.6, .64, 1); }
.nbt-icon svg { width: 21px; height: 21px; }
.nbt-icon svg path, .nbt-icon svg circle { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.nbt-label { font-size: 10.5px; font-weight: 600; transition: color .2s ease, font-weight .2s ease; }
.nbt-badge {
  position: absolute; top: -3px; right: -6px; min-width: 14px; height: 14px; padding: 0 3px;
  border-radius: 999px; background: #f43f5e; color: #fff; font-size: 9px; font-weight: 800;
  display: flex; align-items: center; justify-content: center; line-height: 1;
}
.nbt-tab.is-active { color: #4338ca; }
.nbt-tab.is-active .nbt-icon { transform: translateY(-1px) scale(1.08); }
.nbt-tab.is-active .nbt-label { font-weight: 800; }

@media (prefers-reduced-motion: reduce) {
  .nbt-indicator, .nbt-icon { transition: none; }
}
JavaScript
// 選択タブへインジケーターをスライドさせ、アイコン/ラベルを強調表示する(null安全)
(() => {
  const bar = document.getElementById('nbtTabbar');
  const indicator = document.getElementById('nbtIndicator');
  if (!bar || !indicator) return;
  const tabs = [...bar.querySelectorAll('.nbt-tab')];
  if (!tabs.length) return;

  const move = (tab) => {
    const icon = tab && tab.querySelector('.nbt-icon');
    if (!icon) return;
    const barRect = bar.getBoundingClientRect();
    const r = icon.getBoundingClientRect();
    const size = 40;
    const left = r.left - barRect.left + r.width / 2 - size / 2;
    indicator.style.transform = `translateX(${left}px)`;
    indicator.style.width = size + 'px';
  };

  const select = (tab) => {
    tabs.forEach((t) => { t.classList.remove('is-active'); t.removeAttribute('aria-current'); });
    tab.classList.add('is-active');
    tab.setAttribute('aria-current', 'page');
    move(tab);
  };

  tabs.forEach((tab) => tab.addEventListener('click', () => select(tab)));

  const getActive = () => bar.querySelector('.nbt-tab.is-active') || tabs[0];

  const init = () => {
    indicator.style.transition = 'none';
    move(getActive());
    requestAnimationFrame(() => { indicator.style.transition = ''; });
  };
  if (document.fonts && document.fonts.ready) { document.fonts.ready.then(init); } else { init(); }
  window.addEventListener('resize', () => move(getActive()));
})();

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

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

# 追加してほしい効果
下部タブバー(ヘッダー & ナビ)
モバイルアプリ風の下部固定タブバー。選択したタブへ丸いインジケーターがなめらかにスライドし、アイコンが浮き上がるように強調表示されます。通知バッジ付き。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- モバイルアプリ風の下部固定タブバー。選択タブへインジケーターがスライドし、アイコンが強調表示される -->
<div class="nbt-frame" id="nbtFrame">
  <div class="nbt-app">
    <header class="nbt-appbar"><span>ホーム</span></header>
    <div class="nbt-feed">
      <div class="nbt-post"></div>
      <div class="nbt-post"></div>
      <div class="nbt-post"></div>
    </div>
  </div>

  <nav class="nbt-tabbar" aria-label="下部ナビゲーション" id="nbtTabbar">
    <span class="nbt-indicator" id="nbtIndicator" aria-hidden="true"></span>
    <button class="nbt-tab is-active" type="button" aria-current="page" data-tab="home">
      <span class="nbt-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 11l9-7 9 7"></path><path d="M5 10v10h14V10"></path></svg></span>
      <span class="nbt-label">ホーム</span>
    </button>
    <button class="nbt-tab" type="button" data-tab="search">
      <span class="nbt-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"></circle><path d="M21 21l-4.3-4.3"></path></svg></span>
      <span class="nbt-label">検索</span>
    </button>
    <button class="nbt-tab" type="button" data-tab="post">
      <span class="nbt-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"></path></svg></span>
      <span class="nbt-label">投稿</span>
    </button>
    <button class="nbt-tab" type="button" data-tab="alerts">
      <span class="nbt-icon" aria-hidden="true">
        <svg viewBox="0 0 24 24"><path d="M6 9a6 6 0 0112 0c0 5 2 6 2 6H4s2-1 2-6"></path><path d="M10 20a2 2 0 004 0"></path></svg>
        <span class="nbt-badge">3</span>
      </span>
      <span class="nbt-label">通知</span>
    </button>
    <button class="nbt-tab" type="button" data-tab="me">
      <span class="nbt-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"></circle><path d="M4 20c1.3-4 4.4-6 8-6s6.7 2 8 6"></path></svg></span>
      <span class="nbt-label">マイページ</span>
    </button>
  </nav>
</div>

【CSS】
* { box-sizing: border-box; }
body { margin: 0; display: block; background: #f4f5f8; font-family: "Hiragino Sans", "Segoe UI", system-ui, sans-serif; }

/* ルート: width:100% + height:100vh でカード/モーダルどちらの枠にも隙間なく収まる */
.nbt-frame { position: relative; width: 100%; height: 100vh; overflow: hidden; background: #f4f5f8; }

.nbt-appbar { height: 52px; display: flex; align-items: center; padding: 0 18px; background: #fff; border-bottom: 1px solid #e8e9f0; font-weight: 800; font-size: 15px; color: #1c2033; }
.nbt-feed { padding: 14px; display: flex; flex-direction: column; gap: 12px; height: calc(100% - 52px - 64px); overflow: hidden; }
.nbt-post { height: 90px; border-radius: 14px; background: linear-gradient(120deg, #fff, #eceefb); border: 1px solid #e8e9f0; flex-shrink: 0; }

.nbt-tabbar {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 10;
  display: flex; height: 64px; background: #fff;
  border-top: 1px solid #e8e9f0; box-shadow: 0 -6px 20px rgba(20, 24, 50, .05);
}
.nbt-indicator {
  position: absolute; top: 8px; left: 0; height: 40px; width: 40px; border-radius: 14px;
  background: #eef1ff; transition: transform .3s cubic-bezier(.22, 1, .36, 1), width .3s ease;
  will-change: transform;
}
.nbt-tab {
  position: relative; flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  background: none; border: none; cursor: pointer; padding: 6px 2px;
  color: #9298ab; font: inherit;
}
.nbt-icon { position: relative; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; transition: transform .25s cubic-bezier(.34, 1.6, .64, 1); }
.nbt-icon svg { width: 21px; height: 21px; }
.nbt-icon svg path, .nbt-icon svg circle { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.nbt-label { font-size: 10.5px; font-weight: 600; transition: color .2s ease, font-weight .2s ease; }
.nbt-badge {
  position: absolute; top: -3px; right: -6px; min-width: 14px; height: 14px; padding: 0 3px;
  border-radius: 999px; background: #f43f5e; color: #fff; font-size: 9px; font-weight: 800;
  display: flex; align-items: center; justify-content: center; line-height: 1;
}
.nbt-tab.is-active { color: #4338ca; }
.nbt-tab.is-active .nbt-icon { transform: translateY(-1px) scale(1.08); }
.nbt-tab.is-active .nbt-label { font-weight: 800; }

@media (prefers-reduced-motion: reduce) {
  .nbt-indicator, .nbt-icon { transition: none; }
}

【JavaScript】
// 選択タブへインジケーターをスライドさせ、アイコン/ラベルを強調表示する(null安全)
(() => {
  const bar = document.getElementById('nbtTabbar');
  const indicator = document.getElementById('nbtIndicator');
  if (!bar || !indicator) return;
  const tabs = [...bar.querySelectorAll('.nbt-tab')];
  if (!tabs.length) return;

  const move = (tab) => {
    const icon = tab && tab.querySelector('.nbt-icon');
    if (!icon) return;
    const barRect = bar.getBoundingClientRect();
    const r = icon.getBoundingClientRect();
    const size = 40;
    const left = r.left - barRect.left + r.width / 2 - size / 2;
    indicator.style.transform = `translateX(${left}px)`;
    indicator.style.width = size + 'px';
  };

  const select = (tab) => {
    tabs.forEach((t) => { t.classList.remove('is-active'); t.removeAttribute('aria-current'); });
    tab.classList.add('is-active');
    tab.setAttribute('aria-current', 'page');
    move(tab);
  };

  tabs.forEach((tab) => tab.addEventListener('click', () => select(tab)));

  const getActive = () => bar.querySelector('.nbt-tab.is-active') || tabs[0];

  const init = () => {
    indicator.style.transition = 'none';
    move(getActive());
    requestAnimationFrame(() => { indicator.style.transition = ''; });
  };
  if (document.fonts && document.fonts.ready) { document.fonts.ready.then(init); } else { init(); }
  window.addEventListener('resize', () => move(getActive()));
})();

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

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