Notification UI Components
Use the NotificationsPopover component or build custom notification UIs with the provided React hooks.
MakerKit provides a ready-to-use NotificationsPopover component and React hooks for building custom notification interfaces.
NotificationsPopover
The default notification UI: a bell icon with badge that opens a dropdown list.
import { NotificationsPopover } from '@kit/notifications/components';function AppHeader({ accountId }: { accountId: string }) { return ( <header> <NotificationsPopover accountIds={[accountId]} realtime={false} /> </header> );}Props
| Prop | Type | Required | Description |
|---|---|---|---|
accountIds | string[] | Yes | Account IDs to fetch notifications for |
realtime | boolean | Yes | Enable Supabase Realtime subscriptions |
onClick | (notification) => void | No | Custom click handler |
How accountIds works
Pass all account IDs the user has access to. For a user with a personal account and team memberships:
import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';function NotificationsWithAllAccounts() { const { account, accounts } = useUserWorkspace(); // Include personal account + all team accounts const accountIds = [ account.id, ...accounts.filter(a => !a.is_personal_account).map(a => a.id) ]; return ( <NotificationsPopover accountIds={accountIds} realtime={false} /> );}The built-in layouts handle this automatically. You only need to configure accountIds for custom implementations.
Custom click handling
By default, clicking a notification with a link navigates using an anchor tag. Override this with onClick:
import { useRouter } from 'next/navigation';function CustomNotifications({ accountId }: { accountId: string }) { const router = useRouter(); return ( <NotificationsPopover accountIds={[accountId]} realtime={false} onClick={(notification) => { if (notification.link) { // Custom navigation logic router.push(notification.link); } }} /> );}What the component renders
- Bell icon with red badge showing unread count
- Popover dropdown with notification list on click
- Each notification shows:
- Type icon (info/warning/error with color coding)
- Message body (truncated at 100 characters)
- Relative timestamp ("2 minutes ago", "Yesterday")
- Dismiss button (X icon)
- Empty state when no notifications
The component uses Shadcn UI's Popover, Button, and Separator components with Lucide icons.
React hooks
Build custom notification UIs using these hooks from @kit/notifications/hooks.
useFetchNotifications
Fetches initial notifications and optionally subscribes to real-time updates.
'use client';import { useState, useCallback } from 'react';import { useFetchNotifications } from '@kit/notifications/hooks';type Notification = { id: number; body: string; dismissed: boolean; type: 'info' | 'warning' | 'error'; created_at: string; link: string | null;};function CustomNotificationList({ accountIds }: { accountIds: string[] }) { const [notifications, setNotifications] = useState<Notification[]>([]); const onNotifications = useCallback((newNotifications: Notification[]) => { setNotifications(prev => { // Deduplicate by ID const existingIds = new Set(prev.map(n => n.id)); const unique = newNotifications.filter(n => !existingIds.has(n.id)); return [...unique, ...prev]; }); }, []); useFetchNotifications({ accountIds, realtime: false, onNotifications, }); return ( <ul> {notifications.map(notification => ( <li key={notification.id}>{notification.body}</li> ))} </ul> );}Parameters:
| Parameter | Type | Description |
|---|---|---|
accountIds | string[] | Account IDs to fetch for |
realtime | boolean | Subscribe to real-time updates |
onNotifications | (notifications: Notification[]) => void | Callback when notifications arrive |
Behavior:
- Fetches up to 10 most recent non-dismissed, non-expired notifications
- Uses React Query with
refetchOnMount: falseandrefetchOnWindowFocus: false - Calls
onNotificationswith initial data and any real-time updates
useDismissNotification
Returns a function to dismiss (mark as read) a notification.
'use client';import { useDismissNotification } from '@kit/notifications/hooks';function NotificationItem({ notification }) { const dismiss = useDismissNotification(); const handleDismiss = async () => { await dismiss(notification.id); // Update local state after dismissing }; return ( <div> <span>{notification.body}</span> <button onClick={handleDismiss}>Dismiss</button> </div> );}The function updates the dismissed field to true in the database. RLS ensures users can only dismiss their own notifications.
Notification type
All hooks work with this type:
type Notification = { id: number; body: string; dismissed: boolean; type: 'info' | 'warning' | 'error'; created_at: string; link: string | null;};Building a custom notification center
Full example combining the hooks:
'use client';import { useState, useCallback } from 'react';import { useFetchNotifications, useDismissNotification,} from '@kit/notifications/hooks';type Notification = { id: number; body: string; dismissed: boolean; type: 'info' | 'warning' | 'error'; created_at: string; link: string | null;};export function NotificationCenter({ accountIds }: { accountIds: string[] }) { const [notifications, setNotifications] = useState<Notification[]>([]); const dismiss = useDismissNotification(); const onNotifications = useCallback((incoming: Notification[]) => { setNotifications(prev => { const ids = new Set(prev.map(n => n.id)); const newOnes = incoming.filter(n => !ids.has(n.id)); return [...newOnes, ...prev]; }); }, []); useFetchNotifications({ accountIds, realtime: true, // Enable real-time onNotifications, }); const handleDismiss = async (id: number) => { await dismiss(id); setNotifications(prev => prev.filter(n => n.id !== id)); }; if (notifications.length === 0) { return <p>No notifications</p>; } return ( <div className="space-y-2"> {notifications.map(notification => ( <div key={notification.id} className="flex items-center justify-between p-3 border rounded" > <div> <span className={`badge badge-${notification.type}`}> {notification.type} </span> {notification.link ? ( <a href={notification.link}>{notification.body}</a> ) : ( <span>{notification.body}</span> )} </div> <button onClick={() => handleDismiss(notification.id)}> Dismiss </button> </div> ))} </div> );}This gives you full control over styling and behavior while leveraging the built-in data fetching and real-time infrastructure.
Related documentation
- Notifications overview: Feature overview and architecture
- Configuration: Enable/disable notifications and real-time
- Sending notifications: Create notifications from server code
- Database schema: Table structure and RLS policies