Add privacy policies and cookie consent to React
Install PolicyStack V1, define a shared TypeScript config, render a privacy policy, and collect an analytics consent choice in React 18 or later.
This PolicyStack V1 quickstart adds a privacy policy and a minimal consent choice UI to an existing React 18+ TypeScript application. PolicyStack supplies the state and renderers; you own the banner design and accessibility.
Install the packages
pnpm add @policystack/sdk@1 @policystack/core@1 @policystack/react@1
Alternatively, pnpm dlx @policystack/cli@1 init scaffolds a starter config and an agent reference. The manual example below is complete and uses fictional company details: replace them with your reviewed disclosures.
Define a shared configuration
Create policystack.ts:
import { ContractPrerequisite, defineConfig, LegalBases } from "@policystack/sdk";
export default defineConfig({
company: {
name: "Acme",
legalName: "Acme Ltd",
address: "1 High Street, London",
url: "https://acme.example",
contact: { email: "privacy@acme.example" },
},
effectiveDate: "2026-09-06",
jurisdictions: ["eea", "uk"],
data: {
collected: { Account: ["Email address"] },
context: {
Account: {
purpose: "Create and operate an account",
lawfulBasis: LegalBases.Contract,
retention: "Until account deletion",
provision: ContractPrerequisite("An account cannot be created without it."),
},
},
},
cookies: {
used: { essential: true, analytics: true },
context: {
essential: { lawfulBasis: LegalBases.LegalObligation },
analytics: { lawfulBasis: LegalBases.Consent },
},
},
});
Company values are explicit: V1 does not populate them from package.json. Use configuration guidance and validation to review jurisdiction-dependent disclosures before publishing.
Render the policy and consent controls
Create or replace App.tsx:
import { PolicyStack } from "@policystack/react/provider";
import { PrivacyPolicy } from "@policystack/react/policy";
import { ConsentGate, useConsent } from "@policystack/react/consent";
import policy from "./policystack";
function ConsentControls() {
const { route, acceptAll, acceptNecessary, setRoute } = useConsent();
return (
<section aria-label="Cookie consent">
{route === "cookie" ? (
<>
<p>Allow optional analytics or use necessary cookies only.</p>
<button onClick={() => acceptAll()}>Accept all</button>
<button onClick={() => acceptNecessary()}>Necessary only</button>
</>
) : (
<button onClick={() => setRoute("cookie")}>Change cookie choice</button>
)}
</section>
);
}
export default function App() {
return (
<PolicyStack config={policy}>
<ConsentControls />
<ConsentGate requires="analytics" fallback={<p>Analytics is off.</p>}>
<p>Analytics consent has been granted.</p>
</ConsentGate>
<PrivacyPolicy />
</PolicyStack>
);
}
Verify the behaviour
With no saved choice, the optional analytics gate is closed. Accept all opens it; necessary only keeps it closed. Change the choice to reject after accepting and the gate closes again. The privacy document renders from the same configuration.
The example displays a consent state, not a tracking script. To load a real vendor, use GatedScript and the script factories. Closing a gate does not unload an already executed vendor script: implement the vendor’s opt-out/reset behaviour where needed. See storage and SSR when persisting choices or rendering on the server.
Next steps
- Render a cookie policy or export Markdown.
- Build a preferences panel with useCategory.
- Detect ungated analytics in Vite.
- Validate your configuration and connect MCP.
Implementation: React consent bindings, core consent store.