notiflowsDocs
SDKsClient-side

React

Notiflows React SDK with hooks and components for notification UI

The React SDK (@notiflows/react) provides React hooks and pre-built components for displaying notifications in your application. It's built on top of the JavaScript SDK.

Installation

npm install @notiflows/react
# or
pnpm add @notiflows/react
# or
yarn add @notiflows/react

Quick Start

Import the styles and wrap your application with NotiflowsProvider:

import '@notiflows/react/styles.css';
import {
  NotiflowsProvider,
  FeedRoot,
  FeedTrigger,
  FeedContent,
} from '@notiflows/react';

function App() {
  return (
    <NotiflowsProvider
      apiKey="pk_your_public_key"
      userId="user_123"
      userKey="jwt_token_from_backend"
      channelId="your-channel-id"
    >
      <Header />
      <MainContent />
    </NotiflowsProvider>
  );
}

function Header() {
  return (
    <header>
      <nav>
        <FeedRoot>
          <FeedTrigger />
          <FeedContent />
        </FeedRoot>
      </nav>
    </header>
  );
}

That's it! You now have a fully functional notification bell with a popover feed.

Styles

Default Styles

The SDK ships with a CSS file that provides ready-to-use styling:

import '@notiflows/react/styles.css';

You must import the styles for the pre-built components to render correctly. Import it once in your app's entry point.

Customization with CSS Variables

Override CSS variables to match your brand:

:root {
  /* Colors */
  --nf-primary: #0066cc;
  --nf-primary-foreground: #ffffff;
  --nf-background: #ffffff;
  --nf-foreground: #0f0f0f;
  --nf-muted: #f5f5f5;
  --nf-muted-foreground: #737373;
  --nf-border: #e5e5e5;
  --nf-popover: #ffffff;
  --nf-popover-foreground: #0f0f0f;

  /* Typography */
  --nf-font-family: system-ui, sans-serif;
  --nf-font-size-xs: 0.75rem;
  --nf-font-size-sm: 0.875rem;
  --nf-font-size-base: 1rem;

  /* Spacing & Radius */
  --nf-radius: 0.5rem;
  --nf-radius-sm: 0.25rem;

  /* Shadows */
  --nf-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  --nf-shadow-md: 0 10px 15px -3px rgb(0 0 0 / 0.1);

  /* Special */
  --nf-unread-indicator: #0891b2;
  --nf-badge-bg: #ef4444;
  --nf-badge-text: #ffffff;
}

Dark Mode

Add the dark class to a parent element to enable dark mode:

<html class="dark">
  <!-- Components automatically use dark colors -->
</html>

The SDK includes dark mode styles that are activated when a parent element has the dark class.

Custom Classes

All components accept a className prop for additional styling:

<FeedContent className="my-custom-popover" />
<FeedPanel className="my-custom-panel" />

Provider

The NotiflowsProvider initializes the client and provides context to all child components.

<NotiflowsProvider
  // Required
  apiKey="pk_your_public_key"
  userId="user_123"
  userKey="jwt_token_from_backend"
  channelId="your-channel-id"

  // Optional
  apiUrl="https://api.notiflows.com/user/v1"
  wsUrl="wss://api.notiflows.com/ws/v1"
>
  {children}
</NotiflowsProvider>

The userKey must be generated on your backend using your Application Signing Key. Never expose your signing key in client-side code. See Generating User Tokens for instructions.

Components

Feed Components

The feed components create a popover-based notification center:

import {
  FeedRoot,
  FeedTrigger,
  FeedContent,
  FeedPanel,
  BellButton,
} from '@notiflows/react';

// Minimal setup - uses defaults
<FeedRoot>
  <FeedTrigger />   {/* Renders BellButton by default */}
  <FeedContent />   {/* Renders FeedPanel by default */}
</FeedRoot>

// Explicit setup
<FeedRoot>
  <FeedTrigger>
    <BellButton />
  </FeedTrigger>
  <FeedContent>
    <FeedPanel />
  </FeedContent>
</FeedRoot>

FeedRoot

Wrapper component that manages the popover state. Wraps Radix UI Popover.

<FeedRoot
  open={boolean}           // Controlled open state
  onOpenChange={fn}        // Callback when open state changes
  defaultOpen={boolean}    // Initial open state (uncontrolled)
>
  {children}
</FeedRoot>

FeedTrigger

Button that opens the notification popover. Renders BellButton by default.

// Default
<FeedTrigger />

// Custom trigger
<FeedTrigger>
  <button className="my-button">
    <MyBellIcon />
    <Badge />
  </button>
</FeedTrigger>

FeedContent

Popover content container. Renders FeedPanel by default.

// Default
<FeedContent />

// With custom alignment
<FeedContent align="end" sideOffset={8} />

// With custom content
<FeedContent>
  <FeedPanel NotificationComponent={MyNotification} />
</FeedContent>

FeedPanel

The main notification panel with tabs (All, Unread, Read) and settings.

<FeedPanel
  NotificationComponent={CustomNotification}  // Custom notification renderer
  className="my-panel"
/>

BellButton

Bell icon button with unread badge:

import { BellButton, Badge } from '@notiflows/react';

// Standalone usage
<BellButton onClick={handleClick} />

// Just the badge
<Badge totalUnread={5} />

Notification

Individual notification item component:

import { Notification } from '@notiflows/react';

// Default rendering
<Notification item={entry} />

// With custom action handler
<Notification
  item={entry}
  onActionClick={(url, entry) => {
    // Custom navigation logic
    router.push(url);
  }}
/>

MarkAllAsRead

Button to mark all notifications as read:

import { MarkAllAsRead } from '@notiflows/react';

<MarkAllAsRead onClick={() => markAllAsRead()}>
  Mark all as read
</MarkAllAsRead>

MarkAsArchived

Archive button for individual notifications:

import { MarkAsArchived } from '@notiflows/react';

<MarkAsArchived item={entry} />

Preferences

Preferences panel with toggles for each notiflow:

import { Preferences } from '@notiflows/react';

<Preferences />

Avatar

User avatar with fallback:

import { Avatar } from '@notiflows/react';

<Avatar
  src="https://example.com/avatar.jpg"
  alt="John Doe"
  fallback="JD"
/>

Hooks

useNotiflows

Access the Notiflows client and feed client:

import { useNotiflows } from '@notiflows/react';

function MyComponent() {
  const { client, feedClient } = useNotiflows();

  // Direct API access
  const handleGetPrefs = async () => {
    const prefs = await client.userPreferences().get();
    console.log(prefs);
  };
}

useFeed

Access the notification feed with automatic real-time updates:

import { useFeed } from '@notiflows/react';

function MyComponent() {
  const {
    entries,         // FeedEntry[] - notification items
    totalUnread,     // number - unread count
    totalUnseen,     // number - unseen count
    lastPage,        // pagination info
    isLoading,       // boolean - initial load
    isLoadingMore,   // boolean - loading more items
    error,           // NotiflowsApiError | null
    loadMore,        // () => Promise<void>
    clearError,      // () => void
    retry,           // () => void
  } = useFeed();

  // With options
  const { entries } = useFeed({
    status: FeedEntryStatus.Unread,
    limit: 20,
  });

  return (
    <div>
      <p>{totalUnread} unread notifications</p>
      {entries.map(entry => (
        <div key={entry.id}>{entry.data.body}</div>
      ))}
      {lastPage?.has_more_after && (
        <button onClick={loadMore} disabled={isLoadingMore}>
          Load more
        </button>
      )}
    </div>
  );
}

useNotificationStatus

Update notification states:

import { useNotificationStatus } from '@notiflows/react';

function MyComponent() {
  const {
    markAsSeen,        // (entryId: string) => Promise<void>
    markAsRead,        // (entryId: string) => Promise<void>
    markAsClicked,     // (entryId: string) => Promise<void>
    markAsArchived,    // (entryId: string) => Promise<void>
    markAsUnarchived,  // (entryId: string) => Promise<void>
    markAllAsRead,     // () => Promise<void>
    batchMarkAsRead,   // (entryIds: string[]) => Promise<void>
    batchMarkAsArchived, // (entryIds: string[]) => Promise<void>
  } = useNotificationStatus();

  const handleNotificationClick = async (entry: FeedEntry) => {
    if (!entry.read_at) {
      await markAsRead(entry.id);
    }
    if (entry.data.actionUrl) {
      window.location.href = entry.data.actionUrl;
    }
  };
}

usePreferences

Manage user notification preferences:

import { usePreferences } from '@notiflows/react';

function PreferencesPanel() {
  const {
    preferences,                // Preferences | null
    isLoading,                  // boolean
    updateNotiflowPreferences,  // (notiflowId, prefs) => Promise<void>
  } = usePreferences();

  if (isLoading) return <Spinner />;

  return (
    <div>
      {Object.entries(preferences?.notiflows || {}).map(([id, pref]) => (
        <label key={id}>
          <input
            type="checkbox"
            checked={pref.enabled}
            onChange={() =>
              updateNotiflowPreferences(id, {
                ...pref,
                enabled: !pref.enabled,
              })
            }
          />
          {pref.name}
        </label>
      ))}
    </div>
  );
}

Building Custom UI

Use the hooks to build completely custom notification UIs:

import { useFeed, useNotificationStatus } from '@notiflows/react';

function CustomNotificationCenter() {
  const { entries, totalUnread, isLoading, loadMore } = useFeed();
  const { markAsRead, markAllAsRead } = useNotificationStatus();

  if (isLoading) {
    return <div className="loading">Loading...</div>;
  }

  return (
    <div className="notification-center">
      <header>
        <h2>Notifications</h2>
        <span className="badge">{totalUnread}</span>
        <button onClick={markAllAsRead}>Mark all read</button>
      </header>

      <ul className="notification-list">
        {entries.map(entry => (
          <li
            key={entry.id}
            className={entry.read_at ? 'read' : 'unread'}
            onClick={() => markAsRead(entry.id)}
          >
            {entry.actor && (
              <img
                src={entry.actor.avatar}
                alt={entry.actor.first_name}
              />
            )}
            <div>
              <p>{entry.data.body}</p>
              <time>{formatTime(entry.created_at)}</time>
            </div>
          </li>
        ))}
      </ul>

      <button onClick={loadMore}>Load more</button>
    </div>
  );
}

TypeScript

The SDK is fully typed. Import types as needed:

import type {
  FeedEntry,
  FeedEntryCollection,
  FeedEntryStatus,
  Preferences,
  NotiflowsApiError,
} from '@notiflows/react';

// Re-exported from @notiflows/client
import {
  isApiError,
  BadRequestError,
  UnauthenticatedError,
  NotFoundError,
} from '@notiflows/react';

Error Handling

Handle errors from hooks:

import { useFeed, isApiError } from '@notiflows/react';

function NotificationList() {
  const { entries, error, retry, clearError } = useFeed();

  if (error) {
    return (
      <div className="error">
        <p>Failed to load notifications</p>
        {isApiError(error) && <p>{error.message}</p>}
        <button onClick={retry}>Retry</button>
        <button onClick={clearError}>Dismiss</button>
      </div>
    );
  }

  return (
    <ul>
      {entries.map(entry => (
        <li key={entry.id}>{entry.data.body}</li>
      ))}
    </ul>
  );
}

Next.js Integration

App Router

Use the 'use client' directive for components that use the SDK:

// components/notifications.tsx
'use client';

import '@notiflows/react/styles.css';
import {
  NotiflowsProvider,
  FeedRoot,
  FeedTrigger,
  FeedContent,
} from '@notiflows/react';

interface NotificationsProps {
  userKey: string;
  userId: string;
}

export function Notifications({ userKey, userId }: NotificationsProps) {
  return (
    <NotiflowsProvider
      apiKey={process.env.NEXT_PUBLIC_NOTIFLOWS_API_KEY!}
      channelId={process.env.NEXT_PUBLIC_NOTIFLOWS_CHANNEL_ID!}
      userKey={userKey}
      userId={userId}
    >
      <FeedRoot>
        <FeedTrigger />
        <FeedContent />
      </FeedRoot>
    </NotiflowsProvider>
  );
}
// app/layout.tsx
import { Notifications } from '@/components/notifications';
import { auth, generateUserKey } from '@/lib/auth';

export default async function RootLayout({ children }) {
  const session = await auth();

  // Generate user key on the server
  const userKey = await generateUserKey(session.user.id);

  return (
    <html lang="en">
      <body>
        <nav>
          <Notifications
            userKey={userKey}
            userId={session.user.id}
          />
        </nav>
        {children}
      </body>
    </html>
  );
}

Pages Router

// pages/_app.tsx
import '@notiflows/react/styles.css';

// components/notifications.tsx
import {
  NotiflowsProvider,
  FeedRoot,
  FeedTrigger,
  FeedContent,
} from '@notiflows/react';

export function Notifications({ userKey, userId }) {
  return (
    <NotiflowsProvider
      apiKey={process.env.NEXT_PUBLIC_NOTIFLOWS_API_KEY}
      channelId={process.env.NEXT_PUBLIC_NOTIFLOWS_CHANNEL_ID}
      userKey={userKey}
      userId={userId}
    >
      <FeedRoot>
        <FeedTrigger />
        <FeedContent />
      </FeedRoot>
    </NotiflowsProvider>
  );
}

Browser Support

The SDK supports all modern browsers (ES2022+):

  • Chrome 94+
  • Firefox 93+
  • Safari 15+
  • Edge 94+

Next Steps

On this page