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
| Version | Headline change | Required action |
|---|---|---|
| 3.4.0 | Tailwind utilities replaced with BEM stylesheet; /server + /unstyled entries; <PolicyPage /> Shadow DOM | Add import ".../styles" |
| 3.4.1 | {{var}} substitution fix; theme opt-in dark mode; "use client" actually emitted | None |
| 3.5.0 | validateDsrSubmission; persist alias; rolled-up .d.ts files | None |
| 3.5.1 | PolicyPage TOC anchors work in Shadow DOM | None — 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@latestStep 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
useLocalStorageoption onusePrivacyPolicyanduseDefaultPrivacyPolicy— usepersistinstead.
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
- Styling Architecture — how the new BEM stylesheet works and how to theme it.
- Server Rendering — using
/serverfrom your backend.