Bubble Notification Center バブル通知センター
Layered card notification inbox with dynamic read toggles, real-time unread counter, and status indicators. 3件の通知の既読トグルとリアルタイムな未読カウント管理UI。
ライブデモ Live Demo
Activity Feed
- Build deployed successfully2 mins ago
- Security scan completed: 0 alerts14 mins ago
- Storage usage reached 78%1 hour ago
概要・用途・特徴Overview, Usage & Features
何ができるかWhat it does
Layered card notification inbox with dynamic read toggles, real-time unread counter, and status indicators.
3件の通知の既読トグルとリアルタイムな未読カウント管理UI。
どこで使うかWhere to use
Portfolio, product showcase, landing page.
ポートフォリオ、製品紹介、ランディングページ。
特徴Key features
Layered card notification inbox with dynamic read toggles, real-time unread counter, and status indicators.
3件の通知の既読トグルとリアルタイムな未読カウント管理UI。
調整可能パラメータ Adjustable Parameters
| Parameter | Default | Description |
|---|---|---|
--demo-accent | #818cf8 | Accent color for the panel border and keyboard focus ring. |
実装コード Implementation Code
<div class="demo-stage bn-root" role="region" aria-label="Notification Center"><div class="bn-card"><div class="bn-header"><div class="bn-title-wrap"><span class="bn-icon" aria-hidden="true">●</span><h3 class="bn-title">Activity Feed</h3></div><div class="bn-counter-badge" aria-live="polite"><span class="bn-unread-num">3</span> unread</div></div><ul class="bn-list" role="list"><li class="bn-item bn-unread" data-id="1"><div class="bn-item-bullet" aria-hidden="true"></div><div class="bn-item-main"><span class="bn-item-subject">Build deployed successfully</span><span class="bn-item-time">2 mins ago</span></div><button type="button" class="bn-toggle-btn" aria-pressed="false" aria-label="Toggle read status for deployment notification">Read</button></li><li class="bn-item bn-unread" data-id="2"><div class="bn-item-bullet" aria-hidden="true"></div><div class="bn-item-main"><span class="bn-item-subject">Security scan completed: 0 alerts</span><span class="bn-item-time">14 mins ago</span></div><button type="button" class="bn-toggle-btn" aria-pressed="false" aria-label="Toggle read status for security scan notification">Read</button></li><li class="bn-item bn-unread" data-id="3"><div class="bn-item-bullet" aria-hidden="true"></div><div class="bn-item-main"><span class="bn-item-subject">Storage usage reached 78%</span><span class="bn-item-time">1 hour ago</span></div><button type="button" class="bn-toggle-btn" aria-pressed="false" aria-label="Toggle read status for storage usage notification">Read</button></li></ul></div></div>
<script>
document.querySelectorAll('.bn-root').forEach(root => {
const items = root.querySelectorAll('.bn-item');
const countEl = root.querySelector('.bn-unread-num');
if (!items.length || !countEl) return;
const updateCount = () => {
const unread = root.querySelectorAll('.bn-item.bn-unread').length;
countEl.textContent = unread;
};
items.forEach(item => {
const btn = item.querySelector('.bn-toggle-btn');
if (!btn) return;
btn.addEventListener('click', () => {
const isUnread = item.classList.toggle('bn-unread');
btn.setAttribute('aria-pressed', isUnread ? 'false' : 'true');
btn.textContent = isUnread ? 'Read' : 'Unread';
updateCount();
});
});
});
</script>
.bn-root {
box-sizing: border-box;
width: 100%;
max-width: 560px;
max-height: 420px;
min-width: 0;
margin: 0 auto;
padding: 16px;
background: linear-gradient(135deg, #13141f, #0d0e15);
border-radius: 16px;
border: 1px solid #23253a;
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.6);
font-family: system-ui, -apple-system, sans-serif;
color: #e4e7ec;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.bn-root *, .bn-root *::before, .bn-root *::after {
box-sizing: border-box;
min-width: 0;
}
.bn-root .bn-card {
width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
}
.bn-root .bn-header {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 10px;
border-bottom: 1px solid #1f2233;
flex-wrap: wrap;
gap: 8px;
}
.bn-root .bn-title-wrap {
display: flex;
align-items: center;
gap: 8px;
}
.bn-root .bn-icon {
color: #6366f1;
font-size: 14px;
}
.bn-root .bn-title {
margin: 0;
font-size: 15px;
font-weight: 700;
color: #f8fafc;
}
.bn-root .bn-counter-badge {
font-size: 11px;
font-weight: 600;
background: #312e81;
color: #a5b4fc;
padding: 3px 10px;
border-radius: 12px;
border: 1px solid #4338ca;
}
.bn-root .bn-unread-num {
font-weight: 800;
color: #ffffff;
}
.bn-root .bn-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.bn-root .bn-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
background: #181a28;
border-radius: 10px;
border: 1px solid #282b42;
}
.bn-root .bn-item.bn-unread {
background: #1e2238;
border-color: #3e446d;
}
.bn-root .bn-item-bullet {
width: 8px;
height: 8px;
border-radius: 50%;
background: #475569;
flex-shrink: 0;
}
.bn-root .bn-item.bn-unread .bn-item-bullet {
background: #6366f1;
box-shadow: 0 0 8px #6366f1;
}
.bn-root .bn-item-main {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
}
.bn-root .bn-item-subject {
font-size: 12px;
font-weight: 500;
color: #cbd5e1;
}
.bn-root .bn-item.bn-unread .bn-item-subject {
color: #ffffff;
font-weight: 600;
}
.bn-root .bn-item-time {
font-size: 10px;
color: #64748b;
}
.bn-root .bn-toggle-btn {
appearance: none;
border: 1px solid #374151;
background: #1f2937;
color: #9ca3af;
padding: 5px 12px;
border-radius: 6px;
font-size: 11px;
font-weight: 600;
cursor: pointer;
flex-shrink: 0;
}
.bn-root .bn-item.bn-unread .bn-toggle-btn {
border-color: #4f46e5;
background: #3730a3;
color: #ffffff;
}
.bn-root .bn-toggle-btn:hover {
opacity: 0.9;
}
.bn-root .bn-toggle-btn:focus-visible {
outline: 2px solid #818cf8;
outline-offset: 2px;
}
@media (prefers-reduced-motion: no-preference) {
.bn-root .bn-item {
transition: background 0.25s ease, border-color 0.25s ease, transform 0.15s ease;
}
.bn-root .bn-item-bullet {
transition: background 0.25s ease, box-shadow 0.25s ease;
}
.bn-root .bn-toggle-btn {
transition: background 0.2s ease, border-color 0.2s ease, transform 0.1s ease;
}
.bn-root .bn-toggle-btn:active {
transform: scale(0.95);
}
}
.bn-root{max-height:none;--demo-accent:#818cf8;border-color:var(--demo-accent)}.bn-root button:focus-visible{outline-color:var(--demo-accent)}
.bn-root .bn-item:nth-child(2){margin-left:8px}.bn-root .bn-item:nth-child(3){margin-left:16px}.bn-root .bn-item-bullet{width:24px;height:24px;background:radial-gradient(circle at 30% 25%,#94a3b8,#334155)}.bn-root .bn-item.bn-unread .bn-item-bullet{background:radial-gradient(circle at 30% 25%,#c4b5fd,#6366f1)}
import { useState } from "react";
import "./S-021.css";
function BubbleNotificationCenter() {
const [read, setRead] = useState([false, false, false]);
return <div className="demo-stage bn-root" role="region" aria-label="Notification Center"><div className="bn-card"><div className="bn-header"><div className="bn-title-wrap"><span className="bn-icon" aria-hidden="true">{"\u25CF"}</span><h3 className="bn-title">{"Activity Feed"}</h3></div><div className="bn-counter-badge" aria-live="polite"><span className="bn-unread-num">{read.filter((value) => !value).length}</span>{" unread"}</div></div><ul className="bn-list" role="list"><li data-id="1" className={"bn-item" + (read[0] ? "" : " bn-unread")}><div className="bn-item-bullet" aria-hidden="true" /><div className="bn-item-main"><span className="bn-item-subject">{"Build deployed successfully"}</span><span className="bn-item-time">{"2 mins ago"}</span></div><button type="button" className="bn-toggle-btn" aria-label="Toggle read status for deployment notification" aria-pressed={read[0]} onClick={() => setRead((values) => values.map((value, index) => index === 0 ? !value : value))}>{read[0] ? "Unread" : "Read"}</button></li><li data-id="2" className={"bn-item" + (read[1] ? "" : " bn-unread")}><div className="bn-item-bullet" aria-hidden="true" /><div className="bn-item-main"><span className="bn-item-subject">{"Security scan completed: 0 alerts"}</span><span className="bn-item-time">{"14 mins ago"}</span></div><button type="button" className="bn-toggle-btn" aria-label="Toggle read status for security scan notification" aria-pressed={read[1]} onClick={() => setRead((values) => values.map((value, index) => index === 1 ? !value : value))}>{read[1] ? "Unread" : "Read"}</button></li><li data-id="3" className={"bn-item" + (read[2] ? "" : " bn-unread")}><div className="bn-item-bullet" aria-hidden="true" /><div className="bn-item-main"><span className="bn-item-subject">{"Storage usage reached 78%"}</span><span className="bn-item-time">{"1 hour ago"}</span></div><button type="button" className="bn-toggle-btn" aria-label="Toggle read status for storage usage notification" aria-pressed={read[2]} onClick={() => setRead((values) => values.map((value, index) => index === 2 ? !value : value))}>{read[2] ? "Unread" : "Read"}</button></li></ul></div></div>;
}
export {
BubbleNotificationCenter as default
};
.bn-root {
box-sizing: border-box;
width: 100%;
max-width: 560px;
max-height: 420px;
min-width: 0;
margin: 0 auto;
padding: 16px;
background: linear-gradient(135deg, #13141f, #0d0e15);
border-radius: 16px;
border: 1px solid #23253a;
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.6);
font-family: system-ui, -apple-system, sans-serif;
color: #e4e7ec;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.bn-root *, .bn-root *::before, .bn-root *::after {
box-sizing: border-box;
min-width: 0;
}
.bn-root .bn-card {
width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
}
.bn-root .bn-header {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 10px;
border-bottom: 1px solid #1f2233;
flex-wrap: wrap;
gap: 8px;
}
.bn-root .bn-title-wrap {
display: flex;
align-items: center;
gap: 8px;
}
.bn-root .bn-icon {
color: #6366f1;
font-size: 14px;
}
.bn-root .bn-title {
margin: 0;
font-size: 15px;
font-weight: 700;
color: #f8fafc;
}
.bn-root .bn-counter-badge {
font-size: 11px;
font-weight: 600;
background: #312e81;
color: #a5b4fc;
padding: 3px 10px;
border-radius: 12px;
border: 1px solid #4338ca;
}
.bn-root .bn-unread-num {
font-weight: 800;
color: #ffffff;
}
.bn-root .bn-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.bn-root .bn-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
background: #181a28;
border-radius: 10px;
border: 1px solid #282b42;
}
.bn-root .bn-item.bn-unread {
background: #1e2238;
border-color: #3e446d;
}
.bn-root .bn-item-bullet {
width: 8px;
height: 8px;
border-radius: 50%;
background: #475569;
flex-shrink: 0;
}
.bn-root .bn-item.bn-unread .bn-item-bullet {
background: #6366f1;
box-shadow: 0 0 8px #6366f1;
}
.bn-root .bn-item-main {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
}
.bn-root .bn-item-subject {
font-size: 12px;
font-weight: 500;
color: #cbd5e1;
}
.bn-root .bn-item.bn-unread .bn-item-subject {
color: #ffffff;
font-weight: 600;
}
.bn-root .bn-item-time {
font-size: 10px;
color: #64748b;
}
.bn-root .bn-toggle-btn {
appearance: none;
border: 1px solid #374151;
background: #1f2937;
color: #9ca3af;
padding: 5px 12px;
border-radius: 6px;
font-size: 11px;
font-weight: 600;
cursor: pointer;
flex-shrink: 0;
}
.bn-root .bn-item.bn-unread .bn-toggle-btn {
border-color: #4f46e5;
background: #3730a3;
color: #ffffff;
}
.bn-root .bn-toggle-btn:hover {
opacity: 0.9;
}
.bn-root .bn-toggle-btn:focus-visible {
outline: 2px solid #818cf8;
outline-offset: 2px;
}
@media (prefers-reduced-motion: no-preference) {
.bn-root .bn-item {
transition: background 0.25s ease, border-color 0.25s ease, transform 0.15s ease;
}
.bn-root .bn-item-bullet {
transition: background 0.25s ease, box-shadow 0.25s ease;
}
.bn-root .bn-toggle-btn {
transition: background 0.2s ease, border-color 0.2s ease, transform 0.1s ease;
}
.bn-root .bn-toggle-btn:active {
transform: scale(0.95);
}
}
.bn-root{max-height:none;--demo-accent:#818cf8;border-color:var(--demo-accent)}.bn-root button:focus-visible{outline-color:var(--demo-accent)}
.bn-root .bn-item:nth-child(2){margin-left:8px}.bn-root .bn-item:nth-child(3){margin-left:16px}.bn-root .bn-item-bullet{width:24px;height:24px;background:radial-gradient(circle at 30% 25%,#94a3b8,#334155)}.bn-root .bn-item.bn-unread .bn-item-bullet{background:radial-gradient(circle at 30% 25%,#c4b5fd,#6366f1)}
仕組みとカスタマイズHow It Works & Customization
仕組みHow it works
Scoped CSS creates the visual design; small vanilla JavaScript handlers update the local demo state.
スコープ化したCSSで描画し、Vanilla JavaScriptでデモ内の状態だけを更新します。
カスタマイズ方法Customization
Accent color for the panel border and keyboard focus ring.
パネルの枠線とキーボードフォーカスの色を変更します。
注意点Caveats
The sample content is fictional. No external assets or libraries are required. Motion follows the operating system preference. Demo labels are English; surrounding documentation supports Japanese and English.
デモ内容は架空です。外部素材・ライブラリは不要。OSの動きを減らす設定に対応します。デモ内のラベルは英語、解説は日英対応です。
よくある質問 Frequently Asked Questions
Does this demo need external libraries? 外部ライブラリは必要ですか?
The sample content is fictional. No external assets or libraries are required. Motion follows the operating system preference. Demo labels are English; surrounding documentation supports Japanese and English.
デモ内容は架空です。外部素材・ライブラリは不要。OSの動きを減らす設定に対応します。デモ内のラベルは英語、解説は日英対応です。
AIへの指示テンプレート AI Prompt Template
以下をAIに貼り付けると実装を依頼できます。 Paste the following into your AI assistant to request implementation.