[consent]sub-4kb · v1.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 { PolicyStack } from "@policystack/react/provider";
import { ConsentGate } from "@policystack/react/consent";
import config from "./policystack";

// One config. <PolicyStack> derives the consent categories
// (essential locked, analytics/marketing gated) from config.cookies —
// no separate categories array, no conversion step.
export function App() {
  return (
    <PolicyStack config={config}>
      <YourApp />
      <ConsentGate requires="analytics">
        <GoogleAnalytics />
      </ConsentGate>
    </PolicyStack>
  );
}
[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
[policystack] 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 @policystack/core @policystack/react
CookieBanner.tsx
import { useConsent } from "@policystack/react/consent";

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

Keep the core free, forever.

Consent and Policy are Apache-2.0 and will stay that way — no relicensing, no features held back behind a cloud tier. Sponsorship pays for the time it takes to keep both repos maintained, audited, and worth depending on.