通知パネル Notification Panel

通知一覧を表示するドロップダウンパネル。未読・既読の管理や通知種別の表示を行う。

使用場面: アプリ内通知の一覧表示・未読通知のバッジ表示・通知の既読管理 採用例: Facebook, GitHub

ライブデモ

通知
💬
田中さんがコメントしました
2分前
佐藤さんがあなたの投稿にいいねしました
15分前
デプロイが完了しました
1時間前
👤
鈴木さんがチームに参加しました
昨日

ソースコード

<div class="demo-notifpanel">
  <button class="demo-notifpanel-bell" aria-label="通知を表示">
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
    <span class="demo-notifpanel-badge">3</span>
  </button>
  <div class="demo-notifpanel-dropdown">
    <div class="demo-notifpanel-header">
      <span class="demo-notifpanel-title">通知</span>
      <button class="demo-notifpanel-markall">全て既読にする</button>
    </div>
    <div class="demo-notifpanel-list">
      <div class="demo-notifpanel-item unread" data-id="1">
        <div class="demo-notifpanel-dot"></div>
        <div class="demo-notifpanel-icon" style="background:color-mix(in srgb, var(--ui-primary) 12%, transparent);color:var(--ui-primary);">&#x1F4AC;</div>
        <div class="demo-notifpanel-body">
          <div class="demo-notifpanel-text"><strong>田中さん</strong>がコメントしました</div>
          <div class="demo-notifpanel-time">2分前</div>
        </div>
      </div>
      <div class="demo-notifpanel-item unread" data-id="2">
        <div class="demo-notifpanel-dot"></div>
        <div class="demo-notifpanel-icon" style="background:#fef2f2;color:#ef4444;">&#x2764;</div>
        <div class="demo-notifpanel-body">
          <div class="demo-notifpanel-text"><strong>佐藤さん</strong>があなたの投稿にいいねしました</div>
          <div class="demo-notifpanel-time">15分前</div>
        </div>
      </div>
      <div class="demo-notifpanel-item unread" data-id="3">
        <div class="demo-notifpanel-dot"></div>
        <div class="demo-notifpanel-icon" style="background:#f0fdf4;color:#16a34a;">&#x2714;</div>
        <div class="demo-notifpanel-body">
          <div class="demo-notifpanel-text"><strong>デプロイ</strong>が完了しました</div>
          <div class="demo-notifpanel-time">1時間前</div>
        </div>
      </div>
      <div class="demo-notifpanel-item" data-id="4">
        <div class="demo-notifpanel-dot"></div>
        <div class="demo-notifpanel-icon" style="background:#f5f3ff;color:#7c3aed;">&#x1F464;</div>
        <div class="demo-notifpanel-body">
          <div class="demo-notifpanel-text"><strong>鈴木さん</strong>がチームに参加しました</div>
          <div class="demo-notifpanel-time">昨日</div>
        </div>
      </div>
    </div>
    <div class="demo-notifpanel-footer">
      <a class="demo-notifpanel-viewall" href="#">全ての通知を見る</a>
    </div>
  </div>
</div>
.demo-notifpanel { position: relative; display: inline-block; }
.demo-notifpanel-bell {
  position: relative; background: none; border: 2px solid #e5e7eb; border-radius: 10px;
  padding: 8px 10px; cursor: pointer; color: #333; transition: border-color 0.2s, background 0.2s;
}
.demo-notifpanel-bell:hover { border-color: var(--ui-primary); background: color-mix(in srgb, var(--ui-primary) 5%, transparent); color: var(--ui-primary); }
.demo-notifpanel-badge {
  position: absolute; top: -6px; right: -6px;
  background: var(--ui-primary); color: #fff; font-size: 11px; font-weight: 700;
  width: 20px; height: 20px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  border: 2px solid #fff;
}
.demo-notifpanel-badge.hidden { display: none; }
.demo-notifpanel-dropdown {
  display: none; position: absolute; top: calc(100% + 8px); right: 0;
  background: #fff; border: 1px solid #e5e7eb; border-radius: 14px;
  box-shadow: 0 8px 30px rgba(0,0,0,0.12); z-index: 30; width: 340px;
  animation: demo-notifpanel-in 0.2s ease;
}
.demo-notifpanel-dropdown.open { display: block; }
@keyframes demo-notifpanel-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
.demo-notifpanel-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 16px 10px; border-bottom: 1px solid #f3f4f6;
}
.demo-notifpanel-title { font-size: 15px; font-weight: 700; color: #333; }
.demo-notifpanel-markall {
  background: none; border: none; font-size: 12px; color: var(--ui-primary);
  cursor: pointer; font-weight: 600;
}
.demo-notifpanel-markall:hover { text-decoration: underline; }
.demo-notifpanel-list { max-height: 280px; overflow-y: auto; }
.demo-notifpanel-item {
  display: flex; align-items: flex-start; gap: 10px; padding: 12px 16px;
  cursor: pointer; transition: background 0.15s; position: relative;
}
.demo-notifpanel-item:hover { background: #f9fafb; }
.demo-notifpanel-item.unread { background: color-mix(in srgb, var(--ui-primary) 4%, #fff); }
.demo-notifpanel-dot {
  position: absolute; left: 6px; top: 50%; transform: translateY(-50%);
  width: 6px; height: 6px; border-radius: 50%; background: transparent;
}
.demo-notifpanel-item.unread .demo-notifpanel-dot { background: var(--ui-primary); }
.demo-notifpanel-icon {
  flex-shrink: 0; width: 36px; height: 36px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center; font-size: 16px;
}
.demo-notifpanel-body { flex: 1; min-width: 0; }
.demo-notifpanel-text { font-size: 13px; color: #333; line-height: 1.4; }
.demo-notifpanel-time { font-size: 11px; color: #9ca3af; margin-top: 2px; }
.demo-notifpanel-footer { padding: 10px; border-top: 1px solid #f3f4f6; text-align: center; }
.demo-notifpanel-viewall {
  font-size: 13px; color: var(--ui-primary); text-decoration: none; font-weight: 600;
}
.demo-notifpanel-viewall:hover { text-decoration: underline; }
(function(){
  var bell = document.querySelector('.demo-notifpanel-bell');
  var dropdown = document.querySelector('.demo-notifpanel-dropdown');
  var badge = document.querySelector('.demo-notifpanel-badge');
  var markall = document.querySelector('.demo-notifpanel-markall');
  function updateBadge() {
    var count = document.querySelectorAll('.demo-notifpanel-item.unread').length;
    badge.textContent = count;
    badge.classList.toggle('hidden', count === 0);
  }
  bell.addEventListener('click', function(e) {
    e.stopPropagation();
    dropdown.classList.toggle('open');
  });
  document.querySelectorAll('.demo-notifpanel-item').forEach(function(item) {
    item.addEventListener('click', function() {
      this.classList.remove('unread');
      updateBadge();
    });
  });
  markall.addEventListener('click', function() {
    document.querySelectorAll('.demo-notifpanel-item.unread').forEach(function(item) {
      item.classList.remove('unread');
    });
    updateBadge();
  });
  document.addEventListener('click', function(e) {
    if (!e.target.closest('.demo-notifpanel')) dropdown.classList.remove('open');
  });
  document.addEventListener('keydown', function(e) {
    if (e.key === 'Escape') dropdown.classList.remove('open');
  });
})();

AIプロンプト

Basic
通知パネルをHTML/CSS/JSで作ってください。ベルアイコンをクリックすると通知一覧がドロップダウン表示されます。
Custom
以下の仕様で通知パネルを実装してください。
- ベルアイコンに未読数バッジ表示
- ドロップダウンで通知一覧を表示
- プライマリカラー: #2563eb
- 通知種別ごとのアイコン・カラー(コメント/いいね/システム)
- 未読・既読の状態管理
- 「全て既読にする」ボタン
- クリックで個別既読化
- 通知の相対時間表示(2分前, 1時間前)
Advanced
アクセシビリティ対応の通知パネルを実装してください。
- aria-label で未読件数をベルアイコンに設定
- role="feed" / role="article" による通知リストのセマンティクス
- aria-live="polite" で新着通知を読み上げ
- フォーカス管理(パネル開閉時)
- Escapeキーで閉じる
- 仮想スクロール(大量通知対応)
- リアルタイム通知の追加(WebSocket想定のアニメーション)
- プッシュ通知の許可要求UI
- 通知フィルター(種別・未読のみ)
- レスポンシブ対応(モバイルではフルスクリーン表示)