@opencookies/react
React adapter — useConsent, useCategory, ConsentGate
React adapter for OpenCookies. Wraps @opencookies/core with useSyncExternalStore for concurrent-safe reactivity.
Install#
bun add @opencookies/core @opencookies/reactPeer dependencies: react >= 18.
Setup#
Wrap your app with <OpenCookiesProvider>:
import { OpenCookiesProvider } from "@opencookies/react";
import type { Category } from "@opencookies/core";
import { createRoot } from "react-dom/client";
const categories: Category[] = [
{ key: "essential", label: "Essential", locked: true },
{ key: "analytics", label: "Analytics" },
{ key: "marketing", label: "Marketing" },
];
createRoot(document.getElementById("root")!).render(
<OpenCookiesProvider config={{ categories }}>
<App />
</OpenCookiesProvider>,
);Pass a pre-created store with <OpenCookiesProvider store={store}> instead — useful for SSR-time hydration of decisions from cookies.
API#
useConsent()#
Returns the current consent state plus action methods. Re-renders the consumer when state changes.
import { useConsent } from "@opencookies/react";
function Banner() {
const { route, acceptAll, acceptNecessary, setRoute } = useConsent();
if (route !== "cookie") return null;
return (
<div className="banner">
<button onClick={acceptNecessary}>Necessary only</button>
<button onClick={acceptAll}>Accept all</button>
<button onClick={() => setRoute("preferences")}>Customize</button>
</div>
);
}useCategory(key)#
Granular per-category access. Returns { granted, toggle }.
import { useCategory } from "@opencookies/react";
function AnalyticsToggle() {
const { granted, toggle } = useCategory("analytics");
return (
<label>
<input type="checkbox" checked={granted} onChange={toggle} />
Analytics
</label>
);
}<ConsentGate>#
Renders children when the expression is satisfied; renders fallback otherwise. The component itself emits no DOM wrapper.
import { ConsentGate } from "@opencookies/react";
<ConsentGate requires="analytics" fallback={<EnablePrompt />}>
<Chart />
</ConsentGate>;
<ConsentGate requires={{ and: ["analytics", "marketing"] }}>
<PersonalizedPromo />
</ConsentGate>;The requires shape is a ConsentExpr from core: a category key, { and: [...] }, { or: [...] }, or { not: ... }.
Next.js#
Mark the provider as a client component and mount it in your root layout:
// app/providers.tsx
"use client";
import { OpenCookiesProvider } from "@opencookies/react";
import type { Category } from "@opencookies/core";
const categories: Category[] = [
{ key: "essential", label: "Essential", locked: true },
{ key: "analytics", label: "Analytics" },
];
export function Providers({ children }: { children: React.ReactNode }) {
return <OpenCookiesProvider config={{ categories }}>{children}</OpenCookiesProvider>;
}// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}For SSR-resolved decisions, build the store on the server with the cookie/header storage adapter and pass it via store={store} instead of config={config}.
Shared concepts#
Categories, GPC handling, jurisdiction resolvers, re-consent triggers, script gating (gateScript), and storage adapters all live in @opencookies/core — the React adapter is a thin reactivity wrapper. A working example is in examples/react.
See also#
@opencookies/core— shared concepts and config reference@opencookies/vite— build-time check for ungated cookie / vendor calls- Other adapters — Vue, Solid, Svelte
License#
Apache-2.0