Skip to content

mr-tanta/ndpr-toolkit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

444 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

@tantainnovative/ndpr-toolkit

Compliance infrastructure for the Nigeria Data Protection Act (NDPA) 2023

npm version npm downloads License: MIT TypeScript CI Bundle Size

v5 ships zero-config presets, pluggable storage adapters, compound components, structured-error validators, a compliance score engine, and seven shipped locales (en, yo, ig, ha, pcm, ar, fr) — eight production-ready modules covering consent, data subject rights, DPIA, breach notification, privacy policies, lawful basis, cross-border transfers, and ROPA. 5.2–5.5 add an NDPC GAID 2025 layer: a DCPMI registration classifier, a Compliance Audit Returns scheduler, a Section 40 / Article 33 breach-notification checker (wired live into BreachReportForm), and an ndpr audit CLI that gates compliance in CI.

Documentation | Live Demos | npm | Blog | Latest Release

Open in StackBlitz Open in CodeSandbox

What's new in 5.5: The ndpr audit CLI scores a compliance config against the toolkit engine (compliance score + GAID 2025 DCPMI / CAR / breach checks) and exits non-zero on failure — a drop-in CI gate. The same logic is exported as runNdprAudit / formatNdprAuditReport from /server. See the audit CLI guide.

NDPC GAID 2025 utilities (5.2–5.4): classifyDCPMI (DCPMI registration tier + fee), generateComplianceAuditReturn (CAR initial-audit + 72-hour annual filing schedule), and assessBreachNotification (Section 40 / Article 33 notification completeness) — each pure, React-free, and exposed as a hook. BreachReportForm now renders a live NDPC-notification readiness panel as it's filled in.

Earlier highlights: Structured-result validators are the only shape — { field, code, message }[] with stable codes; uniform onAdd / onUpdate / onArchive callbacks; NDPRDPIA.onResult(result) (5.0). Arabic + French locales with RTL-correct CSS (4.1.0). React 17 dropped; ^18 || ^19 (4.0.0). Full history in the CHANGELOG.

NDPA Toolkit — NDPA Compliance Made Beautiful


Quickstart

Two files. Full NDPA-compliant consent with no backend.

app/layout.tsx

import { NDPRConsent } from '@tantainnovative/ndpr-toolkit/presets';
import '@tantainnovative/ndpr-toolkit/styles';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        {children}
        <NDPRConsent />
      </body>
    </html>
  );
}

That's it — NDPRConsent defaults to localStorageAdapter, so consent persists across page loads with zero backend code.

Want server-side persistence? Pass any StorageAdapter:

import { NDPRConsent } from '@tantainnovative/ndpr-toolkit/presets';
import { cookieAdapter, apiAdapter, composeAdapters, localStorageAdapter } from '@tantainnovative/ndpr-toolkit/adapters';

// Server-readable cookie (best for SSR consent gating)
<NDPRConsent adapter={cookieAdapter('ndpr_consent', { expires: 180 })} />

// Or POST to your own backend
<NDPRConsent adapter={apiAdapter('/api/consent')} />

// Or both — fan-out writes
<NDPRConsent adapter={composeAdapters(
  apiAdapter('/api/consent'),
  localStorageAdapter('ndpr_consent'),
)} />

The full SSR pattern (cookie read server-side → banner hydrates already-resolved, no flash) is in the Server-Side Storage guide.

Consent Management Demo — interactive consent banner with state inspector
Interactive consent demo with configurable position, theme, storage, and real-time state inspector


Install

Pick your package manager:

# pnpm
pnpm add @tantainnovative/ndpr-toolkit

# Bun
bun add @tantainnovative/ndpr-toolkit

# npm
npm install @tantainnovative/ndpr-toolkit

# Yarn
yarn add @tantainnovative/ndpr-toolkit

Add the stylesheet import once in your app entry so components render with default styles:

// app/layout.tsx (Next.js App Router) or src/main.tsx (Vite/CRA/Bun)
import "@tantainnovative/ndpr-toolkit/styles";

The stylesheet is opinionated but token-driven — override any --ndpr-* CSS custom property to theme. Skip this import only if you're using /unstyled to bring your own design system.

Install UI peer dependencies (only needed if you use the higher-level Radix-based components from /presets):

# pnpm
pnpm add @radix-ui/react-switch @radix-ui/react-tabs @radix-ui/react-label @radix-ui/react-slot lucide-react tailwind-merge clsx class-variance-authority

# Bun
bun add @radix-ui/react-switch @radix-ui/react-tabs @radix-ui/react-label @radix-ui/react-slot lucide-react tailwind-merge clsx class-variance-authority

Or scaffold instantly with the CLI:

# Recommended (idiomatic):
npm create ndpr@latest

# Equivalent — pick whichever fits your muscle memory:
npx create-ndpr
npx @tantainnovative/create-ndpr
pnpm create ndpr
bun create ndpr

Bun quickstart

Bun is a first-class runtime for the toolkit. Both common React app shapes work without extra config.

Bun + Vite + React

bun create vite@latest my-ndpr-app --template react-ts
cd my-ndpr-app
bun install
bun add @tantainnovative/ndpr-toolkit
// src/App.tsx
import { NDPRConsent } from '@tantainnovative/ndpr-toolkit/presets/consent';
import '@tantainnovative/ndpr-toolkit/styles';

export default function App() {
  return (
    <>
      <NDPRConsent
        options={[
          { id: 'essential', label: 'Essential', description: 'Required for the site to function', required: true, purpose: 'Site operation' },
          { id: 'analytics', label: 'Analytics', description: 'Anonymous usage measurement', required: false, purpose: 'Product analytics' },
          { id: 'marketing', label: 'Marketing', description: 'Personalised offers and ads', required: false, purpose: 'Marketing communications' },
        ]}
      />
      <main className="p-6">
        <h1 className="text-3xl font-semibold">My NDPA-compliant app</h1>
      </main>
    </>
  );
}
bun dev

Vite is client-only by default — no extra wiring needed. The toolkit's preset components ship with the "use client" directive already injected.

Bun + Next.js 15 (App Router)

bun create next-app@latest my-ndpr-app --typescript --app --tailwind
cd my-ndpr-app
bun add @tantainnovative/ndpr-toolkit

The consent banner is a stateful client component. Mount it from a small client wrapper so the rest of your layout can stay in RSC:

// app/ConsentRoot.tsx
'use client';
import { NDPRConsent } from '@tantainnovative/ndpr-toolkit/presets/consent';

export function ConsentRoot() {
  return (
    <NDPRConsent
      options={[
        { id: 'essential', label: 'Essential', description: 'Required for the site to function', required: true, purpose: 'Site operation' },
        { id: 'analytics', label: 'Analytics', description: 'Anonymous usage measurement', required: false, purpose: 'Product analytics' },
        { id: 'marketing', label: 'Marketing', description: 'Personalised offers and ads', required: false, purpose: 'Marketing communications' },
      ]}
    />
  );
}
// app/layout.tsx
import '@tantainnovative/ndpr-toolkit/styles';
import { ConsentRoot } from './ConsentRoot';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ConsentRoot />
        {children}
      </body>
    </html>
  );
}
bun dev

You don't need to manually mark the import with "use client" from app/layout.tsx — the preset module already ships the directive in its bundled output, so React Server Components import it cleanly via the client wrapper. For RSC-safe, zero-React imports use /server or /core instead.


Choose Your Layer

Pick exactly what your project needs.

Presets — zero-config

Drop-in components with sensible defaults. No configuration required.

import {
  NDPRConsent,           // Consent banner — works with zero props
  NDPRSubjectRights,     // DSR request form
  NDPRBreachReport,      // Breach report form
  NDPRPrivacyPolicy,     // Policy generator wizard
  NDPRDPIA,              // DPIA questionnaire
  NDPRComplianceDashboard, // Visual compliance dashboard
} from '@tantainnovative/ndpr-toolkit/presets';

Components — compound pattern

Full control over layout without rebuilding logic.

import { Consent } from '@tantainnovative/ndpr-toolkit/consent';

<Consent.Provider options={options} onChange={handleSave}>
  <div className="my-layout">
    <Consent.OptionList />
    <div className="flex gap-2">
      <Consent.AcceptButton />
      <Consent.RejectButton />
    </div>
  </div>
</Consent.Provider>

Hooks — headless

Stateful hooks for every module. Bring your own UI entirely.

import { useConsent } from '@tantainnovative/ndpr-toolkit/hooks';

const { hasConsent, acceptAll, rejectAll, shouldShowBanner } = useConsent({ options });

Server — strictly RSC-safe, zero React

The recommended entry for backend and serverless contexts. Pure validators, generators, scoring, locales, adapters, the aggregate compliance audit (runNdprAudit), and the GAID 2025 utilities (classifyDCPMI, generateComplianceAuditReturn, assessBreachNotification) — no React in the import graph. Safe to call from a Next.js Server Component, Edge Function, NestJS controller, or Cloudflare Worker.

import {
  validateConsentStructured,
  validateDsrSubmissionStructured,
  generatePolicyText,
  exportHTML,
  getComplianceScore,
} from '@tantainnovative/ndpr-toolkit/server';

Build-output guard tests assert this entry never carries a "use client" directive and never imports react — the RSC-safety contract is structurally enforced.

Core — types + utilities + Provider

Adds the NDPRProvider React Context on top of /server's pure surface. Use when you want types and validators alongside the provider in the same import.

import { NDPRProvider, validateConsentStructured, getComplianceScore } from '@tantainnovative/ndpr-toolkit/core';

Adapters — pluggable storage

Swap where consent (and other state) is stored without changing any component code.

import { apiAdapter, localStorageAdapter, cookieAdapter } from '@tantainnovative/ndpr-toolkit/adapters';

When NOT to use this toolkit

The toolkit is React-first, NDPA-specific, and built for product engineers shipping a compliant app — not a generic cookie-banner library. Pick something else if:

  • You're not on React. No Vue / Svelte / Angular bindings exist. The /server entry exposes framework-agnostic validators and the compliance-score engine — usable from any Node runtime — but the UI components are React-only.
  • You only need a cookie banner, with no DSR portal, breach reporting, DPIA, ROPA, or compliance scoring. A purpose-built consent library (Iubenda, OneTrust, Cookiebot, Osano) is a better fit and will integrate with your CMP / TCF setup. The toolkit can do the banner alone, but you'd be paying for surface you don't use.
  • Your compliance regime is GDPR / CCPA-primary and you don't operate under the Nigeria Data Protection Act 2023. The validators, statutory deadlines, and section citations are NDPA-specific. (NDPA + GDPR overlap a lot in practice; the toolkit doesn't pretend to be a GDPR product.)
  • You need an enterprise consent-management platform with audit trails, marketing-team UIs, regional CMP exports, and a vendor SLA. That's a different product category.

The toolkit is what we wished existed when building Nigerian SaaS apps in 2025 and need NDPA components that don't fight the rest of the stack. If that's your shape, read on.


Pluggable Storage

Every stateful component accepts an adapter prop. Built-in adapters ship out of the box.

import {
  localStorageAdapter,    // default for browsers
  sessionStorageAdapter,  // cleared on tab close
  cookieAdapter,          // server-readable cookies
  apiAdapter,             // HTTP endpoint (any backend)
  memoryAdapter,          // in-process, good for SSR / tests
  composeAdapters,        // fan-out writes to multiple stores
} from '@tantainnovative/ndpr-toolkit/adapters';

localStorage (default browser behaviour):

<NDPRConsent adapter={localStorageAdapter('ndpr_consent')} />

API endpoint:

<NDPRConsent adapter={apiAdapter('/api/consent', {
  headers: { Authorization: `Bearer ${token}` },
})} />

Write to API + keep a local cache:

import { composeAdapters, apiAdapter, localStorageAdapter } from '@tantainnovative/ndpr-toolkit/adapters';

<NDPRConsent adapter={composeAdapters(
  apiAdapter('/api/consent'),
  localStorageAdapter('ndpr_consent'),
)} />

Cookie (server-readable, for SSR consent gating):

<NDPRConsent adapter={cookieAdapter('ndpr_consent', { expires: 180 })} />

expires defaults to 180 days (since 3.10.5). NDPA Section 26 doesn't pin a number, but 6 months is the common practice for non-essential cookies and matches what regulators have published elsewhere — long enough to avoid daily re-prompting, short enough that consent stays meaningful.


Structured-Result Validators

Every validator returns a typed { field, code, message }[] so client and server code can branch on stable, locale-independent codes — not regex-matched English strings.

Next.js Route Handler:

import { validateDsrSubmissionStructured } from '@tantainnovative/ndpr-toolkit/server';

export async function POST(req: Request) {
  const { valid, errors, data } = validateDsrSubmissionStructured(await req.json());
  if (!valid) {
    return Response.json({ errors }, { status: 422 });
    // errors: Array<{ field: 'dataSubject.email', code: 'email_invalid_format', message: '...' }>
  }
  // `data` is the narrowed `DsrSubmissionPayload`
  await dsrStore.create(data);
  return Response.json({ ok: true }, { status: 201 });
}

Client-side branching:

import { validateConsentStructured } from '@tantainnovative/ndpr-toolkit';

const { valid, errors } = validateConsentStructured(settings);
const stale = errors.find((e) => e.code === 'consent_stale');
if (stale) showRefreshBanner();

Each validator's emitted code values are documented in its JSDoc (and listed in the CHANGELOG 5.0 entry). The legacy string-returning shapes (validateConsent, validateDsrSubmission, formatDSRRequest, validateConsentOptions) were removed in 5.0 — see the 4.1 → 5.0 migration guide if you're upgrading.


Compliance Score

getComplianceScore() evaluates your posture across all 8 NDPA modules and returns a 0–100 score with rated gaps and prioritised recommendations.

import { getComplianceScore } from '@tantainnovative/ndpr-toolkit/core';

const report = getComplianceScore({
  consent: {
    hasConsentMechanism: true,
    hasPurposeSpecification: true,
    hasWithdrawalMechanism: true,
    hasMinorProtection: false,
    consentRecordsRetained: true,
  },
  dsr: {
    hasRequestMechanism: true,
    supportsAccess: true,
    supportsRectification: false,
    supportsErasure: false,
    supportsPortability: false,
    supportsObjection: false,
    responseTimelineDays: 30,
  },
  dpia: { conductedForHighRisk: true, documentedRisks: true, mitigationMeasures: true },
  breach: { hasNotificationProcess: true, notifiesWithin72Hours: true, hasRiskAssessment: true, hasRecordKeeping: true },
  policy: { hasPrivacyPolicy: true, isPubliclyAccessible: true, lastUpdated: '2026-01-01', coversAllSections: true },
  lawfulBasis: { documentedForAllProcessing: true, hasLegitimateInterestAssessment: false },
  crossBorder: { hasTransferMechanisms: true, adequacyAssessed: true, ndpcApprovalObtained: false },
  ropa: { maintained: true, includesAllProcessing: true, lastReviewed: '2026-01-01' },
});

console.log(report.score);         // e.g. 74
console.log(report.rating);        // "good" | "excellent" | "needs-work" | "critical"
console.log(report.recommendations[0].priority); // "critical"

Render a live dashboard:

import { NDPRComplianceDashboard } from '@tantainnovative/ndpr-toolkit/presets';

<NDPRComplianceDashboard
  input={complianceInput}
  showRecommendations
  maxRecommendations={5}
/>

DCPMI & Compliance Audit Returns

Two pure utilities for the NDPC General Application and Implementation Directive (GAID) 2025 registration regime — no React, safe to run server-side or in CI.

classifyDCPMI() derives an organisation's Data Controller/Processor of Major Importance tier from the number of data subjects processed in a six-month window, with its annual registration fee and filing obligations:

import { classifyDCPMI } from '@tantainnovative/ndpr-toolkit/core';

const result = classifyDCPMI({ dataSubjectsInSixMonths: 6200 });

result.tier;                              // "UHL"  (>5,000 → Ultra High Level)
result.isDCPMI;                           // true
result.annualFeeNGN;                      // 250000
result.registration.renewsAnnually;       // false (UHL/EHL register once, file CAR yearly)
result.compliance.auditReturnsAnnual;     // true
result.compliance.initialAuditWithinMonths; // 15
Tier Data subjects / 6 months Annual fee (₦)
UHL — Ultra High Level more than 5,000 250,000
EHL — Extra High Level 1,000 – 5,000 100,000
OHL — Ordinary High Level 200 – 999 10,000
below 200 not a DCPMI by volume

Thresholds and fees are the September 2025 GAID baseline and are configurable (classifyDCPMI(input, { thresholds, fees })) since the NDPC revises them. Pass isDesignated: true for an organisation the Commission has separately listed — it resolves to the 'listed' tier regardless of volume.

generateComplianceAuditReturn() schedules a DCPMI's Compliance Audit Returns — the initial audit due within 15 months of commencement, then the next annual filing deadline (NDPC baseline 31 March, filed via the NDPC Information Management Portal / NIMP):

import { generateComplianceAuditReturn } from '@tantainnovative/ndpr-toolkit/core';

const car = generateComplianceAuditReturn({
  commencementDate: '2025-01-15',
  asOf: '2026-03-21',
  tier: 'UHL',
});

car.schedule.initialAuditDueDate;     // "2026-04-15"  (commencement + 15 months)
car.schedule.nextFilingDeadline;      // "2026-03-31"
car.status.daysUntilNextDeadline;     // 10
car.status.initialAuditDue;           // false

// NDPC deadlines shift — the 2026 filing was extended to 30 May:
generateComplianceAuditReturn(
  { commencementDate: '2025-01-15', asOf: '2026-04-01', tier: 'UHL' },
  { deadlineOverrides: { 2026: '2026-05-30' } },
).schedule.nextFilingDeadline;        // "2026-05-30"

Both ship as memoised hooks for React UIs — useDCPMI(input, options?) and useComplianceAuditReturn(input, options?) from @tantainnovative/ndpr-toolkit/hooks.

These utilities compute registration tiers and filing dates from the GAID 2025 baseline; they are not legal advice. The NDPC revises metrics and extends deadlines — verify against current NDPC guidance before relying on them.


Breach Notification Completeness

assessBreachNotification() checks a BreachReport against the NDPA 2023 Section 40 breach-notification duty as detailed by NDPC GAID 2025 Article 33 — the mandated content of the notification to the Commission, the 72-hour deadline from discovery, and the data-subject communication owed when the risk is high.

import { assessBreachNotification } from '@tantainnovative/ndpr-toolkit/core';

const result = assessBreachNotification(breachReport, {
  asOf: Date.now(),
  assessment: riskAssessment, // optional — high risk triggers the S.40(3) data-subject duty
});

result.complete;             // false until every mandated item is present
result.completeness;         // 0–100 across applicable items
result.missing;              // ["Steps taken to reduce the risk of harm", ...]
result.timing.deadline;      // discoveredAt + 72h
result.timing.hoursRemaining;// time left to notify the NDPC (negative once overdue)
result.timing.overdue;       // true once the 72-hour window has passed
result.dataSubjectCommunicationRequired; // true on high risk (S.40(3))
result.recommendations;      // actionable, each citing its provision

The content checklist (notificationToCommission) maps each item to its source — GAID 2025 Art. 33(5)(a)–(h) for the description, timing, data involved, risk-of-harm, numbers at risk, mitigation, notification steps, and contact point; NDPA S. 40(2) for the data-subject categories and record count. Late filings are flagged with requiresDelayJustification (the NDPC permits phased reporting where full details aren't yet available). Also available as the memoised useBreachNotificationAssessment(report, options?) hook from /hooks.

A documentation-completeness aid, not legal advice — verify against current NDPC guidance.


Cookie Scanner

scanCookies() audits the cookies actually present against the cookies you've declared, surfacing undeclared cookies that put you out of step with your cookie notice (NDPA 2023 S.25–26 / NDPC GAID 2025 — consent must be specific and informed). It's pure and DOM-optional: pass a cookieString (a Cookie: header server-side, or a fixture) or let it read document.cookie in the browser.

import { scanCookies } from '@tantainnovative/ndpr-toolkit/core'; // or /server

const scan = scanCookies(
  [{ name: 'sid', category: 'necessary', provider: 'App', purpose: 'Login session' }],
  { cookieString: document.cookie }, // omit in the browser to read it automatically
);

scan.complete;     // false while any undeclared cookie is present
scan.undeclared;   // cookies on the page you didn't declare — the compliance gap
scan.identified;   // undeclared, but the built-in registry knows them (_ga → Google Analytics, …)
scan.unknown;      // undeclared and unidentifiable
scan.byCategory;   // present cookies grouped by resolved category

A built-in KNOWN_COOKIES registry recognises common third-party cookies (Google Analytics/Ads, Meta, Hotjar, Microsoft Clarity, LinkedIn, Stripe, HubSpot, TikTok, Intercom, …) so even undeclared cookies are usually identified with a provider and likely category; extend it via knownCookies or disable it with useKnownRegistry: false. Your own declarations always take precedence. Also available as the client-side useCookieScan(declared?, options?) hook from /hooks, which scans on mount and returns a stable rescan().

ConsentBanner can surface this directly: pass showCookieScan (plus declaredCookies) and the customize view renders a "Cookies on this page" panel that flags undeclared cookies live — a transparency/self-audit aid for your visitors and DPO.

<ConsentBanner
  options={options}
  onSave={save}
  showCookieScan
  declaredCookies={[{ name: 'sid', category: 'necessary', provider: 'App' }]}
/>

A compliance-discovery aid, not legal advice — verify against current NDPC guidance.


Compliance Audit CLI

The package ships an ndpr binary. ndpr audit scores a compliance config against the toolkit's engine — the compliance score plus the GAID 2025 DCPMI, CAR, and breach-notification checks — and exits non-zero when the audit fails, so you can drop it into CI as a compliance gate.

# with the toolkit installed in your project, the `ndpr` bin is on your PATH:
npx ndpr audit --init        # writes ndpr.audit.json
npx ndpr audit               # run the audit (exit 1 on failure)
npx ndpr audit --min-score 80
npx ndpr audit --json        # machine-readable result

# standalone (no install), use the scoped package name:
npx @tantainnovative/ndpr-toolkit audit --init
NDPA 2023 Compliance Audit
Generated 2026-05-31

Compliance score: 82/100 (good) — minimum 70

✓ Overall compliance score
    82/100 (good); minimum 70.
! Minor (child) data protection controls (Section 31)
    Implement age-verification and parental-consent controls for processing data of minors.
✓ DCPMI registration (GAID 2025)
    Not a Data Controller/Processor of Major Importance by volume.

2 passed, 3 warning(s), 0 failed
Verdict: PASS

The config is { minScore?, compliance, dcpmi?, car?, breaches? }. Critical NDPA gaps and overdue breach notifications hard-fail the audit; high-priority gaps and approaching CAR deadlines warn. In a GitHub Action:

- run: npx ndpr audit --min-score 80

The same logic is exposed programmatically — runNdprAudit(input, options?) and formatNdprAuditReport(result) from @tantainnovative/ndpr-toolkit/server (pure, React-free). See the audit CLI guide.


Backend Integration

CLI scaffolder

Scaffold a complete wiring for your stack in seconds:

npx @tantainnovative/create-ndpr

Detects Next.js (App Router or Pages Router) or Express, prompts for your ORM (Prisma / Drizzle / none), and generates API routes, schema, and layout files — no manual copy-pasting.

Backend recipes

@tantainnovative/ndpr-recipes is a reference implementation with production-ready patterns:

Recipe What you get
prisma/schema.prisma All 5 NDPA compliance tables
src/adapters/prisma-consent.ts Prisma StorageAdapter<ConsentSettings>
src/adapters/drizzle-consent.ts Drizzle StorageAdapter<ConsentSettings>
src/nextjs/app-router/ Consent, DSR, Breach, ROPA, compliance route handlers
src/express/ Full NDPR router with consent, DSR, breach, ROPA routes
src/nextjs/app-router/middleware.ts Next.js consent gate middleware

Copy the files you need into your project. Browse the recipes →


Live Demos

Every module has an interactive demo. No signup, no setup — try them instantly.

8 interactive live demos — zero setup required

Data Subject Rights — 8 rights with request tracking Breach Notification — 72-hour countdown with step-by-step workflow

Left: Data Subject Rights portal with 8 NDPA rights. Right: Breach notification with 72-hour NDPC deadline countdown.

Open any module in your browser (zero install)

Each module ships a minimal Next.js scaffold you can fork in StackBlitz or CodeSandbox. One click → working app demonstrating just that module:

Module NDPA Open in StackBlitz Open in CodeSandbox
Ecommerce starter (full app) §25–27, §34–38 Open Open
Consent §26 Open Open
DSR §34 Open Open
DPIA §28 Open Open
Breach §40 Open Open
Policy §27 Open Open
Lawful Basis §25 Open Open
Cross-Border §41–43 Open Open
RoPA §29 Open Open

Or open the all-in-one example that demos every module in a single app.

SSR-safe consent scaffolds

Cookie-bridged consent that hydrates without a flash. Each scaffold reads the ndpr-consent cookie on the server, seeds the banner's show prop, then lets the browser cookieAdapter take over. See the Server-Side Storage guide for the pattern.

Framework Path Open in StackBlitz
Next.js App Router examples/ssr/nextjs-app-router Open
Remix examples/ssr/remix Open
Astro examples/ssr/astro Open

Internationalization

Wrap your app in <NDPRProvider locale={...}> and every shipped component picks up the translation:

import { NDPRProvider, arabicLocale, frenchLocale } from '@tantainnovative/ndpr-toolkit/core';

<NDPRProvider locale={arabicLocale}>
  <App />
</NDPRProvider>

Seven locales ship out of the box:

Locale Export Notes
English defaultLocale Default
Yoruba yorubaLocale
Igbo igboLocale
Hausa hausaLocale
Nigerian Pidgin pidginLocale
Arabic arabicLocale Modern Standard; RTL-aware. Set dir="rtl" on a parent and styles.css mirrors correctly (logical properties as of 4.1).
French frenchLocale Francophone West African register; uses GDPR-equivalent French terms where they carry meaning.

Override individual strings via mergeLocale(base, partial). Component prop overrides (title, description, etc.) still win over the provider locale — the resolution order is prop → provider locale → English default.


All 8 Modules

Module Import path NDPA reference Key exports
Consent Management /consent Sections 25–26 ConsentBanner, ConsentManager, Consent.*, useConsent
Data Subject Rights /dsr Part VI §34–38 DSRRequestForm, DSRDashboard, useDSR
DPIA /dpia Section 28 DPIAQuestionnaire, DPIAReport, useDPIA
Breach Notification /breach Section 40 BreachReportForm (live readiness panel), BreachRiskAssessment, useBreach, useBreachNotificationAssessment
Privacy Policy /policy Section 27 PolicyGenerator, PolicyPreview, PolicyExporter
Lawful Basis /lawful-basis Section 25 LawfulBasisTracker, useLawfulBasis
Cross-Border Transfers /cross-border Part VIII §41–43 CrossBorderTransferManager, useCrossBorderTransfer
ROPA /ropa Section 29 ROPAManager, useROPA, exportROPAToCSV

Styling & Customization

As of 3.4.0, components ship semantic BEM-style class names (.ndpr-consent-banner, .ndpr-form-field__input, etc.) backed by a real stylesheet. Tailwind is no longer required — the package works in any host so long as you import the stylesheet once.

Default — works in any host:

// Once in your app entry
import "@tantainnovative/ndpr-toolkit/styles";
<ConsentBanner options={options} onSave={handleSave} variant="card" position="bottom" />

Theme via CSS custom properties:

/* Override any --ndpr-* token at :root, [data-theme="dark"], or scoped to a parent. */
:root {
  --ndpr-primary: 22 163 74;          /* RGB triplet — green-600 */
  --ndpr-radius: 1rem;
  --ndpr-font-sans: "Inter", system-ui, sans-serif;
}

Theme via typed JS object — NDPRThemeProvider (new in 3.10.0):

import { NDPRThemeProvider, type NDPRTheme } from '@tantainnovative/ndpr-toolkit';

const theme: NDPRTheme = {
  colors: { primary: '22 163 74', primaryHover: '21 128 61' },
  radius: { base: '0.75rem' },
  font: { sans: '"Inter", system-ui, sans-serif' },
};

<NDPRThemeProvider theme={theme}>
  <App />
</NDPRThemeProvider>

The provider wraps children in a single div with the --ndpr-* variables set inline. Every NDPRTheme field is optional and maps 1:1 to a CSS variable defined in the stylesheet — unset fields fall through to defaults. Same end result as raw CSS overrides; pick what fits your codebase. Full reference in the theming guide.

Light + dark mode auto-switch via prefers-color-scheme, plus an explicit opt-in via data-theme="dark" or .dark on any ancestor (or mode: 'dark' on NDPRThemeProvider).

Per-instance override via slot map:

<ConsentBanner
  options={options}
  onSave={handleSave}
  classNames={{
    root: "my-consent-banner",
    acceptButton: "btn btn-primary",
    rejectButton: "btn btn-secondary",
  }}
/>

Bring your own design system entirely:

import { ConsentBanner } from '@tantainnovative/ndpr-toolkit/unstyled';

<ConsentBanner options={options} onSave={handleSave} classNames={{ /* yours */ }} />

The /unstyled entry defaults unstyled to true, stripping every .ndpr-* class so your CSS applies unfiltered. ARIA, focus management, and data-ndpr-component attributes are preserved (those are part of the contract, not styling).

Each component exports its ClassNames TypeScript interface for autocomplete. Full reference in the docs.


Available Import Paths

Path What you get Dependencies RSC-safe
. (default) Everything react, optional Radix peers for /presets No
/server Pure validators, generators, scoring, the ndpr audit engine + GAID 2025 utilities, locales, adapters, types — zero React tslib Yes
/core Types, utility functions, NDPRProvider react1 Partial
/hooks React hooks for all 8 modules react No
/headless Alias of /hooks — identical exports under a more discoverable name (3.10.0) react No
/presets All zero-config preset components (barrel) react, Radix peers No
/presets/consent Just NDPRConsent — narrower barrel for bundle size react, Radix peers No
/presets/dsr Just NDPRSubjectRights react, Radix peers No
/presets/policy Just NDPRPrivacyPolicy react, Radix peers No
/adapters Storage adapters (localStorage, sessionStorage, cookie, api, memory, composeAdapters) none Yes
/consent ConsentBanner, ConsentManager, Consent.* compound API, useConsent react No
/dsr DSR components + hook react No
/dpia DPIA components + hook react No
/breach Breach components + hook react No
/policy Policy components + hook react, jspdf ≥ 4.2.1, docx (both optional) No
/lawful-basis Lawful basis component + hook react No
/lawful-basis/lite Read-only LawfulBasisTrackerLite — ~65% smaller than /lawful-basis react No
/cross-border Cross-border component + hook react No
/cross-border/lite Read-only CrossBorderTransferManagerLite — ~89% smaller (skips the 624-row adequacy dataset) react No
/ropa ROPA component + hook react No
/ropa/lite Read-only ROPAManagerLite — ~64% smaller than /ropa react No
/unstyled All published components with unstyled defaulted to true react No
/styles Default CSS stylesheet — import "@tantainnovative/ndpr-toolkit/styles" once in your app entry none N/A

PDF / DOCX export peers

PolicyExporter (and exportPDF / exportDOCX from /policy) load jspdf / docx via dynamic import() only when you actually export — they're optional peers, so consumers who don't export documents never install them. If you do export to PDF:

npm install jspdf@^4.2.1 --omit=optional      # npm
pnpm add jspdf@^4.2.1 --no-optional           # pnpm

Use jspdf ≥ 4.2.1 — earlier versions (≤ 4.2.0) carry advisories GHSA-67pg-wm7f-q7fj and GHSA-cjw8-79x6-5cj4, fixed in 4.2.1. The --omit=optional / --no-optional flag drops jspdf's own optional deps (canvg, core-js, dompurify, html2canvas); the toolkit's PDF export uses only core jsPDF text/vector APIs, so it works without them and you get a leaner, dependency-flag-free install.

Bundle size guidance

The toolkit is published with sideEffects: ["*.css"], so a modern bundler (Vite, Next.js / Webpack, esbuild, Bun) will tree-shake unused exports. A few practical rules to keep your bundle small:

  1. Prefer narrow subpaths over the root. import { useConsent } from '@tantainnovative/ndpr-toolkit/hooks' is tighter than the same import from .. The root entry has more transitive exports and bundlers don't always trace them perfectly.

  2. Use the per-preset subpaths when you only need one preset. import { NDPRConsent } from '@tantainnovative/ndpr-toolkit/presets/consent' is ~4 KB; the full /presets barrel is ~8 KB. Same for /presets/dsr and /presets/policy.

  3. The 3 manager components are intentionally heavy (each is ~50 KB src — full table + filter + modal + wizard UIs):

    • LawfulBasisTracker (from /lawful-basis)
    • ROPAManager (from /ropa)
    • CrossBorderTransferManager (from /cross-border)

    If your app only needs the hook (e.g. you're rendering ROPA records inside your own admin UI), import from /hooks instead of the feature subpath — the hook chunk doesn't drag the manager component into your bundle.

    If your page only needs to display records (no Add / Edit / Archive / CSV export), reach for the new Lite variants from /lawful-basis/lite, /cross-border/lite, and /ropa/lite instead — they save ~65%, 89%, and 64% respectively. See Lite vs Full managers.

  4. /server carries zero React. For Server Actions, Route Handlers, scheduled jobs, or compliance-score computation in CI, prefer /server and you'll pay no React-tree cost.


NDPA 2023 Overview

The Nigeria Data Protection Act (NDPA) 2023 replaced the NDPR 2019 and established the Nigeria Data Protection Commission (NDPC) as the independent regulatory body.

Aspect NDPR (2019) NDPA (2023)
Legal status NITDA regulation Act of the National Assembly
Regulator NITDA NDPC (independent commission)
Enforcement Limited Independent investigation and penalty powers
Data subject rights 6 rights 8 rights (added information + automated decision-making)
Cross-border transfers Basic provisions Comprehensive framework with adequacy decisions
Breach notification 72 hours to NITDA 72 hours to NDPC (Section 40)
DPIA Recommended Required for high-risk processing (Section 28)

TypeScript

Written in TypeScript. All types are exported:

import type {
  // Consent
  ConsentOption, ConsentSettings,
  // DSR
  DSRRequest, DSRType, DSRStatus,
  // DPIA
  DPIAQuestion, DPIASection, DPIAResult,
  // Breach
  BreachReport, BreachCategory, RiskAssessment,
  // Policy
  PolicySection, PolicyTemplate, PrivacyPolicy,
  // Lawful Basis
  LawfulBasis, ProcessingActivity,
  // Cross-Border
  CrossBorderTransfer, TransferMechanism,
  // ROPA
  ProcessingRecord, RecordOfProcessingActivities,
  // Compliance score
  ComplianceInput, ComplianceReport, ComplianceRating,
  // Storage
  StorageAdapter,
} from '@tantainnovative/ndpr-toolkit/core';

Contributing

Contributions are welcome. Please read the Contributing Guide before submitting a pull request.


License

MIT


Author

Abraham Esandayinze Tanta — Software Engineer & Data Protection Compliance Specialist

Footnotes

  1. /core re-exports the React NDPRProvider for backward compatibility. For strictly server-side imports use /server — it carries the same pure validators with no React surface.

About

Open-source React & TypeScript library for Nigeria Data Protection Act (NDPA) 2023 compliance. Cookie consent, DSR portal, DPIA, 72-hour breach notification, RoPA, lawful-basis, privacy-policy generator.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors