[opencookies]sub-4kb · pre-1.0

A headless consent state machine.

Tiny core. Adapters for every major framework. A Vite plugin that yells at you when a script sets a cookie behind a category the user hasn’t accepted yet.

App.tsx
import {
  OpenCookiesProvider,
  ConsentGate,
} from "@opencookies/react";

const config = {
  categories: [
    { key: "essential", label: "Essential", locked: true },
    { key: "analytics", label: "Analytics" },
    { key: "marketing", label: "Marketing" },
  ],
};

export function App() {
  return (
    <OpenCookiesProvider config={config}>
      <YourApp />
      <ConsentGate requires="analytics">
        <GoogleAnalytics />
      </ConsentGate>
    </OpenCookiesProvider>
  );
}
[01]what it does

Just the consent layer. Nothing else.

[01]
Headless core
A state machine that tracks categories, persists choices, and emits events. The UI is whatever you build.
[02]
Framework adapters
First-class hooks for React, Vue, Solid, Svelte, and Angular. Same store, same events, framework-idiomatic API.
[03]
Vite plugin
Watches for cookie writes during dev. Throws if a script sets a cookie behind a category the user hasn’t accepted.
[04]
Static scanner
CI step that scans built bundles for ungated cookie usage so things don’t regress between releases.
[05]
Integrations
GA, Meta Pixel, GTM, Hotjar, PostHog — load them gated behind the right consent category by default.
[06]
CLI (planned)
Bootstrap a config from your existing cookies, audit a deployed site, generate a per-environment policy.
[02]dev plugin

Catch leaky cookies before users do.

The Vite plugin patches document.cookie in dev and refuses writes that fall outside the categories the user has accepted — with a stack trace pointing at the line that did it.

vite dev — terminal
! consent violation
[opencookies] ungated cookie write blocked
  cookie:    _ga
  category:  analytics  (not accepted)
  source:    src/lib/analytics.ts:18:5
  fix:       guard with consent.has("analytics")
[03]install

Two lines and you’re shipping.

bash
pnpm add @opencookies/core @opencookies/react
CookieBanner.tsx
import { useConsent } from "@opencookies/react";

export function CookieBanner() {
  const { acceptAll, acceptNecessary } = useConsent();
  return (
    <div>
      <button onClick={acceptAll}>Accept all</button>
      <button onClick={acceptNecessary}>Necessary only</button>
    </div>
  );
}