Notification
Notification は、お知らせ(通知)一覧を表示するコンポーネントです。
未読・既読を分けて表示でき、未読と既読の両方が存在する場合は区切りとして「新規」を表示します。
各通知のレイアウトは itemContent を渡して自由に構成できます。
notification/default is coming soon.
<script lang="ts">
import Notification, { type NotificationItem } from '$lib/components/ui/modules/Notification.svelte';
import { Check, MailCheck } from '@lucide/svelte';
const notifications: NotificationItem[] = [
{
message: 'Rabee UIの問い合わせフォームを公開しました',
icon: MailCheck,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームを公開しました'),
},
{
message: 'Rabee UIの問い合わせフォームを公開しました',
icon: MailCheck,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームを公開しました'),
},
{
message: 'ユーザー1がチームに参加しました',
iconImage: '/images/sample.png',
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('ユーザー1がチームに参加しました'),
},
{
message: 'ユーザー2がチームに参加しました',
iconImage: '/images/sample.png',
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('ユーザー2がチームに参加しました'),
},
{
message: 'Rabee UIの問い合わせフォームに回答がありました',
icon: Check,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームに回答がありました'),
},
{
message: 'Rabee UIの問い合わせフォームに回答がありました',
icon: Check,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームに回答がありました'),
},
];
</script>
<div class="flex items-center justify-center w-full h-screen">
<Notification {notifications}>
{#snippet itemContent(notification)}
<div class="flex gap-3 p-3.25">
<!-- imageとIconの切り替え -->
{#if notification.iconImage}
<img class="shrink-0 size-11 rounded-full object-cover" src={notification.iconImage} alt="" />
{:else}
<div class="flex items-center justify-center shrink-0 size-11 bg-primary/10 rounded-full text-primary">
<notification.icon size="1.25rem" />
</div>
{/if}
<div class="py-0.5 text-left">
<p class="text-foreground text-xs mb-1">{notification.message}</p>
<p class="text-subtle-foreground text-xs">{notification.createdAt}</p>
</div>
</div>
{/snippet}
</Notification>
</div>
プロパティ
Notificationは、以下のプロパティをサポートしています。
| 名前 | 型 | デフォルト値 | 説明 |
|---|---|---|---|
notifications |
NotificationItem[] |
表示する通知一覧です。 | |
itemContent |
Snippet<[NotificationItem]> |
表示内容を描画する Snippet です(引数に notification を受け取ります)。 |
NotificationItemは、以下のプロパティをサポートしています。
| 名前 | 型 | デフォルト値 | 説明 |
|---|---|---|---|
unread |
boolean |
未読かどうかを表します。 | |
message |
string |
通知本文です。 | |
createdAt |
string |
作成日です。 | |
icon |
any |
アイコンコンポーネントです。 | |
iconImage |
string |
アイコン画像 URL です。 | |
onClick |
() => void |
クリック時に呼ばれるコールバック関数です。 |
インストールの手順
以下のコンポーネントのコードを、使いたいプロジェクトにコピー&ペーストします。
パスは実際のプロジェクトの構成にあわせて更新します。
modules/Notification.svelte
<!--
@component
## 概要
- お知らせ一覧を表示するコンポーネントです
## 機能
- お知らせ一覧を表示
## Props
- notification: お知らせデータ
- itemContent: 表示内容を描画する snippet
## Usage
```svelte
<Notification {notifications} >
{#snippet itemContent(notification)}
{/snippet}
</Notification>
```
-->
<script module lang="ts">
import type { Snippet } from 'svelte';
export interface NotificationProps {
/** 通知一覧 */
notifications: NotificationItem[];
itemContent: Snippet<[NotificationItem]>;
}
export interface NotificationItem {
/** 未読かどうか */
unread: boolean;
/** テキスト */
message: string;
/** 作成日 */
createdAt: string;
/** アイコン */
icon?: any;
/** アイコンイメージ */
iconImage?: string;
/** コールバック関数 */
onClick?: () => void;
}
</script>
<script lang="ts">
let { notifications, itemContent }: NotificationProps = $props();
// 未読→既読の順で並べ替え
const sorted = $derived([...notifications].sort((a, b) => Number(b.unread) - Number(a.unread)));
// 既読が始まる最初のindex(未読しかないなら -1)
const firstReadIndex = $derived(sorted.findIndex((notification) => !notification.unread));
// 未読の最後のindex(未読/既読が両方ある時だけ有効)
const lastUnreadIndex = $derived(firstReadIndex > 0 ? firstReadIndex - 1 : -1);
// 既読の最後のindex
const lastIndex = $derived(sorted.length - 1);
</script>
<div class="w-80 max-h-72 bg-surface border border-border rounded-md overflow-hidden overflow-y-auto" data-rabee-ui="notification">
{#if notifications.length}
{#each sorted as notification, index}
{#if index === firstReadIndex && firstReadIndex !== 0}
<!-- 未読と既読の区切り(未読も既読もある時だけ表示になる) -->
<div class="relative h-px -mt-px">
<span class="absolute top-1/2 left-1/2 px-1.5 text-destructive text-xs pointer-events-none -translate-x-1/2 -translate-y-1/2">
新規
</span>
<div class="absolute top-1/2 right-1/2 left-0 h-px bg-destructive mr-4 -translate-y-1/2"></div>
<div class="absolute top-1/2 right-0 left-1/2 h-px bg-destructive ml-4 -translate-y-1/2"></div>
</div>
{/if}
<!-- 1件分の通知行(borderの有無を index に応じて切り替え) -->
<div class={[index === lastIndex || index === lastUnreadIndex ? 'border-b-0' : 'border-b border-border/50']}>
<!-- 未読/既読でレイアウトを変更 -->
<button class={['size-full outline-ring cursor-pointer focus-visible:relative focus-visible:z-10 focus-visible:rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring', notification.unread ? 'bg-primary/5 hover:bg-primary/10' : 'hover:bg-accent/90']} type="button" onclick={() => notification.onClick?.()}>
<!-- 通知1件分の表示内容(レイアウト含めて使用側の itemContent に委譲) -->
{@render itemContent(notification)}
</button>
</div>
{/each}
{:else}
<!-- empty -->
<div class="p-2">
<div class="flex items-center justify-center w-full py-16 bg-muted/10 rounded-lg">
<p class="text-foreground/70 text-xs">まだ通知がありません</p>
</div>
</div>
{/if}
</div>
使い方
notification/default is coming soon.
<script lang="ts">
import Notification, { type NotificationItem } from '$lib/components/ui/modules/Notification.svelte';
import { Check, MailCheck } from '@lucide/svelte';
const notifications: NotificationItem[] = [
{
message: 'Rabee UIの問い合わせフォームを公開しました',
icon: MailCheck,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームを公開しました'),
},
{
message: 'Rabee UIの問い合わせフォームを公開しました',
icon: MailCheck,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームを公開しました'),
},
{
message: 'ユーザー1がチームに参加しました',
iconImage: '/images/sample.png',
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('ユーザー1がチームに参加しました'),
},
{
message: 'ユーザー2がチームに参加しました',
iconImage: '/images/sample.png',
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('ユーザー2がチームに参加しました'),
},
{
message: 'Rabee UIの問い合わせフォームに回答がありました',
icon: Check,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームに回答がありました'),
},
{
message: 'Rabee UIの問い合わせフォームに回答がありました',
icon: Check,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームに回答がありました'),
},
];
</script>
<div class="flex items-center justify-center w-full h-screen">
<Notification {notifications}>
{#snippet itemContent(notification)}
<div class="flex gap-3 p-3.25">
<!-- imageとIconの切り替え -->
{#if notification.iconImage}
<img class="shrink-0 size-11 rounded-full object-cover" src={notification.iconImage} alt="" />
{:else}
<div class="flex items-center justify-center shrink-0 size-11 bg-primary/10 rounded-full text-primary">
<notification.icon size="1.25rem" />
</div>
{/if}
<div class="py-0.5 text-left">
<p class="text-foreground text-xs mb-1">{notification.message}</p>
<p class="text-subtle-foreground text-xs">{notification.createdAt}</p>
</div>
</div>
{/snippet}
</Notification>
</div>
サンプル
Default
Notification の基本的な表示サンプルです。
各通知の表示内容は itemContent の Snippet で構成し、アイコン・アバター、本文、日時などを自由にレイアウトできます。
notification/default is coming soon.
<script lang="ts">
import Notification, { type NotificationItem } from '$lib/components/ui/modules/Notification.svelte';
import { Check, MailCheck } from '@lucide/svelte';
const notifications: NotificationItem[] = [
{
message: 'Rabee UIの問い合わせフォームを公開しました',
icon: MailCheck,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームを公開しました'),
},
{
message: 'Rabee UIの問い合わせフォームを公開しました',
icon: MailCheck,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームを公開しました'),
},
{
message: 'ユーザー1がチームに参加しました',
iconImage: '/images/sample.png',
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('ユーザー1がチームに参加しました'),
},
{
message: 'ユーザー2がチームに参加しました',
iconImage: '/images/sample.png',
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('ユーザー2がチームに参加しました'),
},
{
message: 'Rabee UIの問い合わせフォームに回答がありました',
icon: Check,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームに回答がありました'),
},
{
message: 'Rabee UIの問い合わせフォームに回答がありました',
icon: Check,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームに回答がありました'),
},
];
</script>
<div class="flex items-center justify-center w-full h-screen">
<Notification {notifications}>
{#snippet itemContent(notification)}
<div class="flex gap-3 p-3.25">
<!-- imageとIconの切り替え -->
{#if notification.iconImage}
<img class="shrink-0 size-11 rounded-full object-cover" src={notification.iconImage} alt="" />
{:else}
<div class="flex items-center justify-center shrink-0 size-11 bg-primary/10 rounded-full text-primary">
<notification.icon size="1.25rem" />
</div>
{/if}
<div class="py-0.5 text-left">
<p class="text-foreground text-xs mb-1">{notification.message}</p>
<p class="text-subtle-foreground text-xs">{notification.createdAt}</p>
</div>
</div>
{/snippet}
</Notification>
</div>
Read-Unread
Notification の 未読/既読表示のサンプルです。
未読の通知は上側にまとめて表示し、既読の通知は下側に表示します。
未読と既読の両方が存在する場合は、間に「新規」の区切りを表示します。
notification/read-unread is coming soon.
<script lang="ts">
import Notification, { type NotificationItem } from '$lib/components/ui/modules/Notification.svelte';
import { Check, MailCheck } from '@lucide/svelte';
const notifications: NotificationItem[] = [
{
message: 'Rabee UIの問い合わせフォームを公開しました',
icon: MailCheck,
createdAt: '2026/01/02 15:00',
unread: true,
onClick: () => alert('Rabee UIの問い合わせフォームを公開しました'),
},
{
message: 'Rabee UIの問い合わせフォームを公開しました',
icon: MailCheck,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームを公開しました'),
},
{
message: 'ユーザー1がチームに参加しました',
iconImage: '/images/sample.png',
createdAt: '2026/01/02 15:00',
unread: true,
onClick: () => alert('ユーザー1がチームに参加しました'),
},
{
message: 'ユーザー2がチームに参加しました',
iconImage: '/images/sample.png',
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('ユーザー2がチームに参加しました'),
},
{
message: 'Rabee UIの問い合わせフォームに回答がありました',
icon: Check,
createdAt: '2026/01/02 15:00',
unread: true,
onClick: () => alert('Rabee UIの問い合わせフォームに回答がありました'),
},
{
message: 'Rabee UIの問い合わせフォームに回答がありました',
icon: Check,
createdAt: '2026/01/02 13:00',
unread: false,
onClick: () => alert('Rabee UIの問い合わせフォームに回答がありました'),
},
];
</script>
<div class="flex items-center justify-center w-full h-screen">
<Notification {notifications}>
{#snippet itemContent(notification)}
<div class="flex gap-3 p-3.25">
<!-- imageとIconの切り替え -->
{#if notification.iconImage}
<img class="shrink-0 size-11 rounded-full object-cover" src={notification.iconImage} alt="" />
{:else}
<div class="flex items-center justify-center shrink-0 size-11 bg-primary/10 rounded-full text-primary">
<notification.icon size="1.25rem" />
</div>
{/if}
<div class="py-0.5 text-left">
<p class="text-foreground text-xs mb-1">{notification.message}</p>
<p class="text-subtle-foreground text-xs">{notification.createdAt}</p>
</div>
</div>
{/snippet}
</Notification>
</div>