通知一覧Notification center
ベルのボタンを押すと開く、届いた通知を新しい順に並べたパネル。未読の印と時刻が付き、「すべて既読にする」でまとめて読んだことにできる。
表示例
アイコン: Font Awesome 7 Free(fa-solid / fa-regular)
別名・英語表記
- 通知センター
- お知らせパネル
- 通知パネル
- お知らせ一覧
- ベルのメニュー
- 通知の履歴
- Notification center
実装例
<div class="x-notification-center">
<button class="x-notification-center__bell" type="button" aria-label="お知らせ(未読2件)"
aria-expanded="true" aria-controls="x-notification-center-p">
<i class="fa-solid fa-bell" aria-hidden="true"></i>
<span class="x-notification-center__n" aria-hidden="true">2</span>
</button>
<!-- 見本なので開いた状態で流れの中に置いている。実際は section に popover 属性、ベルに popovertarget="x-notification-center-p" を付けて開閉し、
aria-expanded は false から始めて toggle イベントで書き換える(hidden 属性では閉じない) -->
<section class="x-notification-center__p" id="x-notification-center-p" aria-labelledby="x-notification-center-h">
<div class="x-notification-center__head">
<h2 class="x-notification-center__h" id="x-notification-center-h">お知らせ</h2>
<button class="x-notification-center__all" type="button">すべて既読にする</button>
</div>
<ul role="list" class="x-notification-center__list">
<li>
<a class="x-notification-center__a x-notification-center__a--new" href="/notice/3/">
<span class="x-notification-center__dot" aria-hidden="true"></span>
<span class="x-notification-center__sr">未読:</span>
<span class="x-notification-center__t">ご注文の商品を発送しました</span>
<time class="x-notification-center__time" datetime="2026-09-25T10:20">10分前</time>
</a>
</li>
<li>
<a class="x-notification-center__a x-notification-center__a--new" href="/notice/2/">
<span class="x-notification-center__dot" aria-hidden="true"></span>
<span class="x-notification-center__sr">未読:</span>
<span class="x-notification-center__t">コメントに返信がありました</span>
<time class="x-notification-center__time" datetime="2026-09-25T08:05">2時間前</time>
</a>
</li>
<li>
<a class="x-notification-center__a" href="/notice/1/">
<span class="x-notification-center__t">メンテナンスのお知らせ</span>
<time class="x-notification-center__time" datetime="2026-09-23">9月23日</time>
</a>
</li>
</ul>
</section>
</div>
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-notification-center,
.x-notification-center *:not([class*="fa-"]),
.x-notification-center *:not([class*="fa-"])::before,
.x-notification-center *:not([class*="fa-"])::after {
box-sizing: border-box;
/* 貼った先の既定の書体に引きずられないようにする。書体が変わると
文字の幅と高さが変わり、枠のサイズまでずれる */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}
.x-notification-center {
display: flex;
flex-direction: column;
align-items: flex-end;
width: 290px;
color: #1b2028;
}
.x-notification-center__bell {
position: relative;
width: 40px;
height: 40px;
border: 1px solid #7f8a99;
border-radius: 50%;
background: #fff;
color: #1b2028;
font-size: 16px;
cursor: pointer;
}
.x-notification-center__n {
position: absolute;
top: -4px;
right: -4px;
min-width: 18px;
height: 18px;
padding: 0 4px;
border: 2px solid #fff;
border-radius: 9px;
background: #b91c1c;
color: #fff;
font-size: 10px;
font-weight: 700;
line-height: 14px;
}
.x-notification-center__p {
width: 100%;
margin-top: 6px;
border: 1px solid #cfd4dc;
border-radius: 10px;
background: #fff;
overflow: hidden;
}
.x-notification-center__head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 8px 6px 12px;
border-bottom: 1px solid #e3e6ea;
}
.x-notification-center__h {
margin: 0;
font-size: 12.5px;
font-weight: 700;
}
.x-notification-center__all {
min-height: 26px;
padding: 2px 6px;
border: 0;
border-radius: 6px;
background: none;
color: #1d4ed8;
font: inherit;
font-size: 11px;
cursor: pointer;
}
.x-notification-center__list {
margin: 0;
padding: 0;
list-style: none;
}
.x-notification-center__list li + li {
border-top: 1px solid #e3e6ea;
}
.x-notification-center__a {
display: grid;
position: relative;
grid-template-columns: 8px 1fr;
column-gap: 8px;
padding: 8px 12px;
color: #1b2028;
text-decoration: none;
}
.x-notification-center__a--new {
background: #eff4ff;
}
.x-notification-center__dot {
grid-row: 1 / 3;
width: 8px;
height: 8px;
margin-top: 5px;
border-radius: 50%;
background: #1d4ed8;
}
.x-notification-center__t,
.x-notification-center__time {
grid-column: 2;
}
.x-notification-center__t {
font-size: 12px;
}
.x-notification-center__a--new .x-notification-center__t {
font-weight: 700;
}
.x-notification-center__time {
font-size: 12px;
color: #56606f;
}
.x-notification-center__sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.x-notification-center__a:hover .x-notification-center__t {
text-decoration: underline;
}
.x-notification-center__bell:focus-visible,
.x-notification-center__all:focus-visible,
.x-notification-center__a:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: -2px;
}
.x-notification-center__bell:focus-visible {
outline-offset: 2px;
}
AIへの指示文
「通知一覧(Notification center)」を実装してください。
## これは何か
ベルのボタンを押すと開く、届いた通知を新しい順に並べたパネル。未読の印と時刻が付き、「すべて既読にする」でまとめて読んだことにできる。
## 使いどころ
- 会員向けのサービスで、発送・返信・お知らせなど複数の通知をあとから読み返せるようにしたいとき
- トーストのように消える通知を見逃した人に、履歴を残しておきたいとき
- 件数だけを示すバッジや通知ドットの先に、中身の一覧を置きたいとき
## 実装の要件
- button[aria-expanded][aria-controls](アイコン + 件数の丸)+ パネル(見出し + 「すべて既読にする」+ ul > li > a)
- 各項目は 未読の印 + 題名 + time[datetime]。未読の項目は背景と太字も変える
- 実際のページではパネルに popover 属性を付け、ベルの popovertarget で開く。閉じた状態を hidden 属性で持たない(popover と併せると、開いても表示されない)
- 外側を押す・Esc で閉じる動きと、Esc で閉じたときにフォーカスを開く前の要素(キーボードで開いたならベル)へ戻す動きはブラウザが持つ(Chrome・Edge は 114 から、Safari は 17 から、Firefox は 125 から。iOS の Safari が外側のタップで閉じるのは 18.3 から)
- ベルの click で先に this.focus() を呼ぶ。Safari と macOS の Firefox はマウスで押したボタンにフォーカスを移さず、そのままだとパネルの中から Esc で閉じたときにベルへ戻らない
- aria-expanded はパネルの toggle イベントの newState(open / closed)に合わせて true / false に書き換え、外側クリックや Esc で閉じたときも古い値を残さない
- 位置は見本の margin-top も含めて @supports (position-area: block-end) の中だけに書く。ベルの popovertarget で開いたパネルは、ベルが暗黙のアンカーになる(Chrome・Edge は 133 から、Safari は 26 から、Firefox は 147 から)
- @supports の中では margin: 6px 0 0; inset: auto; position-area: block-end span-inline-start; position-try-fallbacks: flip-block; を指定し、ベルの右端にそろえて下に出す
- position-area に対応しないブラウザ向けに、@supports not (position-area: block-end) の中でパネルに margin: auto; を書き、画面の中央に出す(popover の既定の margin: auto に頼ると、margin を 0 にするリセット CSS に消されて画面の端に張りつく)
- 幅は見本の width: 100% をやめ、パネル自身に width: min(290px, 100vw - 32px) で持たせる。popover の既定の padding(0.25em)は 0 に戻す(戻さないと枠の内側に白い縁が出る)
- 見本の overflow: hidden を overflow-y: auto に替え、高さを抑えて中でスクロールさせる。抑えないと、画面の下にはみ出した通知と「すべて見る」に届かない
- @supports の中では max-height: calc(100% - 6px); position-try-order: most-block-size; を足し、ベルの上下のうち広い側に収める(100% はベルから画面の端までの高さ、6px は隙間。position-try-order は Firefox は 148 から)
- @supports not の中では画面の中央に出るので、max-height: calc(100dvh - 32px) で抑える
- 開いただけで全部を既読にしない。どれを読んだかが分からなくなる。既読にするのは項目を開いたときと「すべて既読にする」を押したとき
- 件数を無制限に積まない。新しい順に20件ほどを出し、残りは「すべて見る」の一覧ページへ送る
- 時刻は「10分前」のような相対表記にするなら、time 要素の datetime に正確な日時を持たせる
- 狭い画面ではパネルが画面からはみ出す。幅を画面に合わせるか、通知の一覧ページへ移す
## アクセシビリティの要件
- ベルのボタンの名前に未読の件数を含める(「お知らせ(未読2件)」)。数字の丸は aria-hidden="true" にする
- ベルのボタンに aria-expanded と aria-controls(パネルの id)を付ける。Esc で閉じ、フォーカスをベルに戻す
- 未読を色だけで示さない。印の有無と太字に加え、読み上げ用に「未読:」の文字を添える
- 新しい通知が届いたことは、ベルの名前を更新したうえで role="status" の要素で控えめに伝える。パネルを勝手に開かない
## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。
```html
<div class="x-notification-center">
<button class="x-notification-center__bell" type="button" aria-label="お知らせ(未読2件)"
aria-expanded="true" aria-controls="x-notification-center-p">
<i class="fa-solid fa-bell" aria-hidden="true"></i>
<span class="x-notification-center__n" aria-hidden="true">2</span>
</button>
<!-- 見本なので開いた状態で流れの中に置いている。実際は section に popover 属性、ベルに popovertarget="x-notification-center-p" を付けて開閉し、
aria-expanded は false から始めて toggle イベントで書き換える(hidden 属性では閉じない) -->
<section class="x-notification-center__p" id="x-notification-center-p" aria-labelledby="x-notification-center-h">
<div class="x-notification-center__head">
<h2 class="x-notification-center__h" id="x-notification-center-h">お知らせ</h2>
<button class="x-notification-center__all" type="button">すべて既読にする</button>
</div>
<ul role="list" class="x-notification-center__list">
<li>
<a class="x-notification-center__a x-notification-center__a--new" href="/notice/3/">
<span class="x-notification-center__dot" aria-hidden="true"></span>
<span class="x-notification-center__sr">未読:</span>
<span class="x-notification-center__t">ご注文の商品を発送しました</span>
<time class="x-notification-center__time" datetime="2026-09-25T10:20">10分前</time>
</a>
</li>
<li>
<a class="x-notification-center__a x-notification-center__a--new" href="/notice/2/">
<span class="x-notification-center__dot" aria-hidden="true"></span>
<span class="x-notification-center__sr">未読:</span>
<span class="x-notification-center__t">コメントに返信がありました</span>
<time class="x-notification-center__time" datetime="2026-09-25T08:05">2時間前</time>
</a>
</li>
<li>
<a class="x-notification-center__a" href="/notice/1/">
<span class="x-notification-center__t">メンテナンスのお知らせ</span>
<time class="x-notification-center__time" datetime="2026-09-23">9月23日</time>
</a>
</li>
</ul>
</section>
</div>
```
```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-notification-center,
.x-notification-center *:not([class*="fa-"]),
.x-notification-center *:not([class*="fa-"])::before,
.x-notification-center *:not([class*="fa-"])::after {
box-sizing: border-box;
/* 貼った先の既定の書体に引きずられないようにする。書体が変わると
文字の幅と高さが変わり、枠のサイズまでずれる */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}
.x-notification-center {
display: flex;
flex-direction: column;
align-items: flex-end;
width: 290px;
color: #1b2028;
}
.x-notification-center__bell {
position: relative;
width: 40px;
height: 40px;
border: 1px solid #7f8a99;
border-radius: 50%;
background: #fff;
color: #1b2028;
font-size: 16px;
cursor: pointer;
}
.x-notification-center__n {
position: absolute;
top: -4px;
right: -4px;
min-width: 18px;
height: 18px;
padding: 0 4px;
border: 2px solid #fff;
border-radius: 9px;
background: #b91c1c;
color: #fff;
font-size: 10px;
font-weight: 700;
line-height: 14px;
}
.x-notification-center__p {
width: 100%;
margin-top: 6px;
border: 1px solid #cfd4dc;
border-radius: 10px;
background: #fff;
overflow: hidden;
}
.x-notification-center__head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 8px 6px 12px;
border-bottom: 1px solid #e3e6ea;
}
.x-notification-center__h {
margin: 0;
font-size: 12.5px;
font-weight: 700;
}
.x-notification-center__all {
min-height: 26px;
padding: 2px 6px;
border: 0;
border-radius: 6px;
background: none;
color: #1d4ed8;
font: inherit;
font-size: 11px;
cursor: pointer;
}
.x-notification-center__list {
margin: 0;
padding: 0;
list-style: none;
}
.x-notification-center__list li + li {
border-top: 1px solid #e3e6ea;
}
.x-notification-center__a {
display: grid;
position: relative;
grid-template-columns: 8px 1fr;
column-gap: 8px;
padding: 8px 12px;
color: #1b2028;
text-decoration: none;
}
.x-notification-center__a--new {
background: #eff4ff;
}
.x-notification-center__dot {
grid-row: 1 / 3;
width: 8px;
height: 8px;
margin-top: 5px;
border-radius: 50%;
background: #1d4ed8;
}
.x-notification-center__t,
.x-notification-center__time {
grid-column: 2;
}
.x-notification-center__t {
font-size: 12px;
}
.x-notification-center__a--new .x-notification-center__t {
font-weight: 700;
}
.x-notification-center__time {
font-size: 12px;
color: #56606f;
}
.x-notification-center__sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.x-notification-center__a:hover .x-notification-center__t {
text-decoration: underline;
}
.x-notification-center__bell:focus-visible,
.x-notification-center__all:focus-visible,
.x-notification-center__a:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: -2px;
}
.x-notification-center__bell:focus-visible {
outline-offset: 2px;
}
```
## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。
使いどころ
- 会員向けのサービスで、発送・返信・お知らせなど複数の通知をあとから読み返せるようにしたいとき
- トーストのように消える通知を見逃した人に、履歴を残しておきたいとき
- 件数だけを示すバッジや通知ドットの先に、中身の一覧を置きたいとき
注意点
- 開いただけで全部を既読にしない。どれを読んだかが分からなくなる。既読にするのは項目を開いたときと「すべて既読にする」を押したとき
- 件数を無制限に積まない。新しい順に20件ほどを出し、残りは「すべて見る」の一覧ページへ送る
- 時刻は「10分前」のような相対表記にするなら、time 要素の datetime に正確な日時を持たせる
- 狭い画面ではパネルが画面からはみ出す。幅を画面に合わせるか、通知の一覧ページへ移す
アクセシビリティの要点
- ベルのボタンの名前に未読の件数を含める(「お知らせ(未読2件)」)。数字の丸は aria-hidden="true" にする
- ベルのボタンに aria-expanded と aria-controls(パネルの id)を付ける。Esc で閉じ、フォーカスをベルに戻す
- 未読を色だけで示さない。印の有無と太字に加え、読み上げ用に「未読:」の文字を添える
- 新しい通知が届いたことは、ベルの名前を更新したうえで role="status" の要素で控えめに伝える。パネルを勝手に開かない