libconsent (built-in)
Consent kept in the browser, banner drawn by your own components.
The default: no external script, consent kept in the browser, and the UI rendered by your own components. It is the only adapter that supports per-service switches.
import { ConsentProvider } from "@libconsent/core";
<ConsentProvider>{children}</ConsentProvider>;Configure it by passing it explicitly:
import { local } from "@libconsent/core";
const adapter = local({ revision: "2026-01", reloadOnRevoke: true });
<ConsentProvider adapter={adapter}>{children}</ConsentProvider>;Build the adapter outside your component or in a useMemo. A new adapter
object means a new connection.
Options
| Option | Default | |
|---|---|---|
storageKey | "libconsent" | Where the record is kept. |
revision | — | Bump it and everyone who consented under a different value is asked again. |
categories | all four | Which categories to offer. |
storage | localStorageAdapter | See below. |
reloadOnRevoke | false | Reload after consent is withdrawn, so scripts that already ran actually stop. |
What gets stored
{
"version": 1,
"updatedAt": "2026-01-14T09:12:33.000Z",
"revision": "2026-01",
"categories": { "essential": true, "analytics": true, "marketing": false },
"services": { "hotjar": false }
}updatedAt records when the decision was made, which you generally need to be
able to show. Anything that doesn't parse, or was written under a different
revision or schema version, counts as no consent.
Cookie storage
localStorage isn't visible to your server. For middleware, RSCs or anything
else that needs the same decision:
import { local, cookieStorage } from "@libconsent/core";
const adapter = local({ storage: cookieStorage() });cookieStorage({ maxAge, path }) writes a SameSite=Lax cookie holding the
same JSON.
Your own storage
Three methods and a subscription:
import type { ConsentStorage } from "@libconsent/core";
const sessionStorageAdapter: ConsentStorage = {
read: (key) => sessionStorage.getItem(key),
write: (key, value) => sessionStorage.setItem(key, value),
clear: (key) => sessionStorage.removeItem(key),
};subscribe(key, onChange) is optional; it is what keeps other tabs in sync.
Cross-tab behaviour
Consent changed in another tab applies immediately: the banner closes, gated components appear, and an open preferences dialog resets its draft instead of saving over the newer decision.