COMPLIANCE AS CODE FRAMEWORK

Consent isn't a checkbox.
It's a system.

OpenPolicy is the consent and compliance layer for your app. Privacy policy, cookie banner, and consent log — generated from your TypeScript, versioned with your code.

v0.0.30 · Apache-2.0

BLOG.md / react

Build Privacy Policies Your Customers Actually Want to Read

Published
2026-03-18
Author
OpenPolicy Team
Reading
3 min · 706 words
Share

Render your privacy policy directly into your React, Next.js, or TanStack app as components you fully control — swap every heading, paragraph, and section for your own.

Policy pages are always an afterthought. An unstyled wall of text, pasted from a Google Doc, completely disconnected from the rest of your product — your nav, your fonts, your brand. Every other page in your app got a design review. Your privacy policy got a Ctrl+V.

That matters more than it used to. Users care more about where their data goes, and regulators across the US, EU, and beyond are holding companies to higher standards. A policy page that looks like it was abandoned in 2014 signals you haven’t thought seriously about either.

@openpolicy/react lets you render your policy content directly into your React, Next.js, or TanStack app as native components. Same design system, same fonts, same attention to detail as everything else you ship.

Install

bun add @openpolicy/sdk @openpolicy/react

Define your policies

Create a single openpolicy.ts at the root of your project. The unified defineConfig() lets you define all policies in one file with a shared company block:

// openpolicy.ts
import { ContractPrerequisite, defineConfig, LegalBases, Voluntary } from "@openpolicy/sdk";

export default defineConfig({
	company: {
		name: "Acme",
		legalName: "Acme, Inc.",
		address: "123 Main St, San Francisco, CA 94105",
		contact: { email: "privacy@acme.com" },
	},
	effectiveDate: "2026-03-18",
	jurisdictions: ["eu", "us-ca"],
	data: {
		collected: {
			"Account information": ["Email address", "Display name"],
			"Usage data": ["Pages visited", "Session duration"],
		},
		context: {
			"Account information": {
				purpose: "To create and manage user accounts",
				lawfulBasis: LegalBases.Contract,
				retention: "Until account deletion",
				provision: ContractPrerequisite("We cannot create or operate your account."),
			},
			"Usage data": {
				purpose: "To understand product usage and improve the service",
				lawfulBasis: LegalBases.LegitimateInterests,
				retention: "13 months",
				provision: Voluntary("None — your service is unaffected."),
			},
		},
	},
	thirdParties: [
		{ name: "Vercel", purpose: "Hosting and edge delivery" },
		{ name: "Plausible", purpose: "Privacy-friendly analytics" },
	],
	cookies: {
		used: { essential: true, analytics: true, marketing: false },
		context: {
			essential: { lawfulBasis: LegalBases.LegalObligation },
			analytics: { lawfulBasis: LegalBases.Consent },
			marketing: { lawfulBasis: LegalBases.Consent },
		},
	},
	automatedDecisionMaking: [],
});

Render it directly into your app

Wrap your page in <OpenPolicy> and drop in <PrivacyPolicy />. Components are unstyled by default and emit data-op-* attributes (data-op-heading, data-op-section, data-op-paragraph, data-op-list, data-op-policy) as styling hooks — target them from Tailwind or your own CSS. The TanStack example’s /tailwind and /css-vars routes show both approaches.

import { OpenPolicy, PrivacyPolicy } from "@openpolicy/react";
import openpolicy from "./openpolicy";

export function PrivacyPolicyPage() {
	return (
		<OpenPolicy config={openpolicy}>
			<PrivacyPolicy />
		</OpenPolicy>
	);
}

That’s a working privacy policy page. No build script, no generated files. The same flow works for cookies — swap in <CookiePolicy /> and you’re done.

Make it yours

Every part of the document is customisable — headings, paragraphs, sections, links. Pass your own components and OpenPolicy uses them instead of the defaults. Here’s a practical example with plain Tailwind classes:

const Section = ({ section, children }) => (
	<section id={section.id} className="mb-10 border-b pb-10 last:border-b-0">
		{children}
	</section>
);

const Heading = ({ node }) => {
	const Tag = `h${node.level}` as "h2" | "h3";
	return <Tag className="text-xl font-semibold mb-3 text-gray-900">{node.value}</Tag>;
};

const Paragraph = ({ children }) => (
	<p className="text-gray-600 leading-relaxed mb-4">{children}</p>
);

export function PrivacyPolicyPage() {
	return (
		<main className="max-w-2xl mx-auto px-6 py-16">
			<OpenPolicy config={openpolicy}>
				<PrivacyPolicy components={{ Section, Heading, Paragraph }} />
			</OpenPolicy>
		</main>
	);
}

Why this is better than a static page

  • Builds trust. A policy page that matches your product’s design tells users you care about the details — including the ones that protect them.
  • Type-safe. Every field is checked by TypeScript. You can’t ship a policy with a missing contact email.
  • Structured. Each section is generated from your actual config — no stale placeholder text.
  • Version-controlled. The config lives in your repo. git blame shows you when and why anything changed.
  • Jurisdiction-aware. Set jurisdictions: ["eu"] and GDPR-required sections (right to erasure, data transfers, DPA contact) are included automatically.

We want to see what you build

Policy pages don’t have to be ugly. If you ship a custom policy page with OpenPolicy, share it — open an issue on GitHub, tag us, or post it wherever you talk about the things you build. We’re collecting examples and would love to feature yours.