Upgrading from 3.3.x

What changed in v3.4 and v3.5, what's deprecated, and what you need to do (mostly: add one import line).

TL;DR

The toolkit is backward-compatible at the component API level from 3.3.x to 3.5.x — every prop, ARIA attribute, and behaviour from 3.3.1 still works. The only required change for most consumers is one import line for the stylesheet:

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

If you previously had Tailwind configured to scan node_modules/@tantainnovative/*, you can remove that contententry — it's no longer doing anything.

What landed in each version

VersionHeadline changeRequired action
3.4.0Tailwind utilities replaced with BEM stylesheet; /server + /unstyled entries; <PolicyPage /> Shadow DOMAdd import ".../styles"
3.4.1{{var}} substitution fix; theme opt-in dark mode; "use client" actually emittedNone
3.5.0validateDsrSubmission; persist alias; rolled-up .d.ts filesNone
3.5.1PolicyPage TOC anchors work in Shadow DOMNone — automatic

Step 1: Bump the version

pnpm add @tantainnovative/ndpr-toolkit@latest
# or
npm install @tantainnovative/ndpr-toolkit@latest
# or
bun add @tantainnovative/ndpr-toolkit@latest

Step 2: Import the stylesheet

Add one line to your app entry. Required for default styles to render — without it the components emit semantic class names but nothing styles them.

// app/layout.tsx (Next.js App Router)
import "@tantainnovative/ndpr-toolkit/styles";

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

From a CSS file (globals.css, Tailwind v4 entrypoint, etc.) you can also use @import:

@import "@tantainnovative/ndpr-toolkit/styles";

Step 3: Audit your style overrides

Components no longer emit Tailwind utility classes. If your CSS targeted those (e.g. .bg-white on the consent banner), update to the new BEM selectors:

Old (3.3.x)New (3.4.0+)
[role="dialog"].bg-white.ndpr-consent-banner
.text-2xl.font-bold (banner title).ndpr-consent-banner__title
Button utility classes.ndpr-consent-banner__button--primary / --secondary

Better still: target the stable data-ndpr-component attributes — see the styling architecture guide.

Optional API improvements you may want to adopt

persist over useLocalStorage

The clearer name persist is now canonical on usePrivacyPolicy and useDefaultPrivacyPolicy. The old useLocalStorage still works as a deprecated alias and will be removed in 4.0.

// Before
useDefaultPrivacyPolicy({ orgInfo, useLocalStorage: false });

// After
useDefaultPrivacyPolicy({ orgInfo, persist: false });

<ConsentBanner variant>

Three visual variants: bar (default, full-width strip), card (bounded floating card with margin from edges), modal (centered with backdrop overlay).

<ConsentBanner
  options={options}
  onSave={handleSave}
  variant="card"
  position="bottom"
/>

<PolicyPage> mode + theme

The new component renders a complete privacy policy with full styling. Defaults to Shadow DOM (no host CSS leak) in light mode (no surprise dark theme on macOS users):

// Drop-in widget
<PolicyPage policy={policy} />

// Inline mode for SEO/SSR — pair with @tailwindcss/typography
<article className="prose prose-slate dark:prose-invert max-w-3xl mx-auto">
  <PolicyPage policy={policy} mode="inline" />
</article>

// Follow OS dark-mode preference (opt-in)
<PolicyPage policy={policy} options={{ theme: 'auto' }} />

Server-side validation with validateDsrSubmission

Replace ad-hoc zod or class-validator schemas in your DSR route handler with the toolkit-shipped validator. Mirrors the client-side rules in <DSRRequestForm /> exactly.

// app/api/dsr/route.ts
import { validateDsrSubmission } from '@tantainnovative/ndpr-toolkit/server';

export async function POST(req: Request) {
  const result = validateDsrSubmission(await req.json());
  if (!result.valid) return Response.json({ errors: result.errors }, { status: 422 });
  // result.data is the typed DsrSubmissionPayload
  await dsrStore.create(result.data);
  return Response.json({ ok: true }, { status: 201 });
}

See the server-rendering guide for NestJS and Express examples.

Deprecations slated for 4.0

  • useLocalStorage option on usePrivacyPolicy and useDefaultPrivacyPolicy — use persist instead.

That's the entire deprecation list — the API surface has been remarkably stable.

Troubleshooting

Components look unstyled after upgrade

You forgot the stylesheet import. Add import "@tantainnovative/ndpr-toolkit/styles" in your app entry. CSS class names are present in the markup but no rules are loaded.

PolicyPage renders dark on macOS dark mode users

You're likely on 3.4.0 — upgrade to 3.4.1 or later. v3.4.1 made dark mode opt-in via options={{ theme: 'auto' }}.

Privacy policy shows literal {{orgName}}

You're on 3.4.0 — upgrade to 3.4.1 or later. The substitution path was broken in 3.4.0 and fixed in 3.4.1.

TOC anchor links don't scroll inside PolicyPage

Upgrade to 3.5.1 — the click delegate that scrolls to the matching shadow-root section was added there.

Next steps