The banner
Install the shadcn/ui banner and preferences dialog, or build your own.
libconsent can work with your existing banner. However, if you don't have one, libconsent ships a ready-made banner and preferences dialog that you can drop in!
libconsent's banner is a shadcn/ui registry item — the files are copied into your project and you edit them like your own components.
npx shadcn@latest add https://vantezzen.github.io/libconsent/r/consent.jsonThat writes components/consent/ — a banner, a preferences dialog, a floating
trigger and the strings — and installs the shadcn primitives they use.
import { ConsentProvider } from "@libconsent/core";
import { ConsentBanner } from "@/components/consent";
<ConsentProvider>
{children}
<ConsentBanner privacyPolicyUrl="/privacy" />
</ConsentProvider>;Running the real provider. Answer the banner and watch the right column.
const { categories } = useConsent();- essential: true
- functional: false
- analytics: false
- marketing: false
<CookieService id="google-analytics"> <GoogleAnalytics gaId="G-XXXXXXX" /></CookieService>nothing rendered — no analytics consent
<CookieService id="youtube" fallback={<Poster />}> <YouTubeEmbed id="dQw4w9WgXcQ" /></CookieService>Opening the preferences from elsewhere
<ConsentBanner> renders both the banner and the preferences dialog, so a
"Cookie settings" link in your footer needs no state of its own:
function Footer() {
const { openSettings } = useConsent();
return <button onClick={openSettings}>Cookie settings</button>;
}Props
| Prop | Default | |
|---|---|---|
variant | "bar" | "bar" is a corner card; "dialog" opens the full preferences straight away. |
position | "bottom-right" | "bottom-left" and "bottom-center" also available. |
privacyPolicyUrl | — | Linked from both layers. Omitted, the link is left out. |
messages | — | Overrides any of the strings. |
The service list
The dialog lists the services your app gates. Add a <CookieService id="hotjar">
anywhere and Hotjar appears under Analytics with its own switch; remove it and
the row goes away. There is no separate list to keep in sync.
Per-service switches appear when the consent platform accepts them. Keeping consent in the browser does; most platforms take category-level answers only, and the dialog then offers categories alone.
Text and translations
By default, the banner ships with english. However there are more translations available - simply pull them from the shadcn registry:
npx shadcn@latest add https://vantezzen.github.io/libconsent/r/consent-locales.jsonimport { de } from "@/components/consent/locales";
<ConsentBanner messages={de} />;Eleven languages are included: ar, de, es, fr, it, ja, ko, nl,
pt, ru, zh. Overrides are merged field by field, so a single string is a
valid messages object:
<ConsentBanner messages={{ acceptAll: "Sounds good" }} />Building your own
useConsentForm() holds the unsaved draft and the rules that act on it, so a
dialog of your own is markup and event handlers:
function Preferences() {
const form = useConsentForm();
return (
<form onSubmit={(e) => (e.preventDefault(), form.save())}>
{form.categories.map((row) => (
<label key={row.category}>
<input
type="checkbox"
checked={row.granted}
disabled={row.locked}
onChange={(e) => row.toggle(e.target.checked)}
/>
{row.category}
</label>
))}
<button disabled={!form.dirty}>Save</button>
</form>
);
}Mount it inside something that unmounts when the dialog closes and the draft resets itself. Toggling a category also clears the per-service switches under it, so the category control always wins.
useConsentForm() needs a platform that accepts consent from your own UI —
the built-in adapter, or Cookiebot. On the others it throws and points you at
openSettings(), which opens their dialog instead.