UI Widget — Configuration
new ConsentiSetup(config) accepts a single ConsentiConfig object. All top-level keys are optional — the widget works with an empty config object, auto-detecting the compliance group from the browser.
Minimal config to get started
This is all you need for a fully working consent banner. Everything below is optional.
import { ConsentiSetup } from '@consenti/ui'
// Auto-detects compliance from browser locale (GDPR, CCPA, etc.)
const widget = new ConsentiSetup({})
// Or pin a specific compliance group:
// new ConsentiSetup({ compliance: { type: 'opt-in' } }) // GDPR
// new ConsentiSetup({ compliance: { type: 'opt-out' } }) // CCPAFull configuration reference
Every available option shown with its default value.
import { ConsentiSetup } from '@consenti/ui'
const widget = new ConsentiSetup({
// ── Compliance group (optional) ──────────────────────────────────────────────
compliance: {
type: 'opt-in', // see Compliance groups table below
geoDataProvider: undefined, // custom function to resolve visitor country
ageGate: {
minAge: 18,
defaultDeny: true,
},
},
// ── Core behaviour (optional) ───────────────────────────────────────────────
core: {
tenantId: 'my-site', // logical identifier for this installation
locale: 'en', // BCP 47; falls back to language prefix, then 'en'
autoHonorGPC: true, // false | true | 'strict'
storage: 'cookie', // 'cookie' | 'localStorage'
cookieDomains: '.example.com',
allowReceipt: true,
disableCssTemplate: false,
userId: 'server-assigned-uuid', // authenticated users only
usePrebuiltProfiles: true, // load pre-built profiles instead of constructing from config
cacheResolvedProfiles: true, // cache resolved profile in sessionStorage (1h TTL)
console: ['error', 'warn'], // log levels to emit; 'error' | 'warn' | 'info' | 'debug'
theme: {
bgColor: '#ffffff',
textColor: '#1a1a1a',
primaryColor: '#1565c0',
primaryTextColor: '#ffffff',
secondaryColor: '#f0f4f8',
secondaryTextColor: '#1a3460',
borderColor: '#e2e8f0',
accentColor: '#d32f2f',
accentTextColor: '#ffffff',
fontFamily: 'system-ui, sans-serif',
fontSizeBase: '14px',
fontSizeHeading: '18px',
fontSizeMultiplier: '1',
borderRadius: '8px',
buttonBorderRadius: '4px',
toggleBgOn: '#1565c0',
toggleBgOff: '#cccccc',
},
},
// ── Mount point (optional) ───────────────────────────────────────────────────
rootEl: '#consenti-root', // CSS selector or HTMLElement; omit to use document.body
// ── Dark mode (optional) ─────────────────────────────────────────────────────
darkMode: false, // true = apply dark colour tokens to the widget
// ── Auto Initialize widget (optional) ────────────────────────────────────────
autoInit: true,
// ── Hide Powered By Consenti text from banner/modal (optional) ───────────────
hidePoweredBy: false,
// ── Backend API (optional) ──────────────────────────────────────────────────
api: {
enabled: true,
baseUrl: 'https://your-site.com',
authToken: '',
tenantId: 'my-site', // tenant identifier sent with API requests
complianceGroup: 'opt-in', // skip auto-resolution; always fetch this group's profile
trustDomain: false, // bypass domain allowlist check (dev/test only)
},
// ── Integrations (optional) ─────────────────────────────────────────────────
utils: {
gtm: {
containerId: 'GTM-XXXXXX',
dataLayer: 'dataLayer',
events: [], // [] = all events
urlPassthrough: true,
adsDataRedaction: false,
},
},
// ── Frontend plugins (optional) ─────────────────────────────────────────────
plugins: [],
// ── Runtime profile overrides (optional) ────────────────────────────────────
profileOverride: {
mainBanner: { position: 'top' },
},
})compliance
Selects which consent model the widget applies. When omitted, Consenti auto-detects the appropriate group from the browser's navigator.language and optional geo data.
| Key | Type | Default | Description |
|---|---|---|---|
type | string | auto-detected | Compliance group key. See the Compliance groups table below. |
geoDataProvider | WidgetCountryResolverFn | undefined | Custom async function that returns the visitor's ISO 3166-1 alpha-2 country code. Used to improve auto-detection when the default locale-based heuristic is not accurate enough. Called once per session. |
ageGate | AgeGateWidgetConfig | undefined | Age gate configuration. When set, the widget shows an age-verification step before the consent banner. minAge defaults to 18.defaultDeny pre-denies all non-mandatory cookies when the user is under-age. |
Compliance groups
| type | Model | Covered regulations | Notes |
|---|---|---|---|
opt-in | Opt-in | GDPR, UK GDPR, PIPEDA, POPIA, PDPA-TH, APPI, KVKK | Banner on first visit; all non-mandatory cookies denied until granted. Default when no type given and browser locale maps to EU/EEA. |
opt-out | Opt-out | CCPA, US state laws | All cookies default to granted; consent written silently; no banner unless user visits a "Do Not Sell" page. |
opt-out-strict | Strict Opt-out | CPRA (California 2023) | Supersedes CCPA. Opt-out for sale/sharing; opt-in required for sensitive data. GPC triggers both Do Not Sell and Do Not Share. |
opt-in-dpdpa | Opt-in (DPDPA) | DPDPA (India 2023) | Fiduciary name + grievance officer rendered in modal. Age gate required for children under 18. GPC signal ignored. |
opt-in-china | Opt-in (China) | PIPL (China 2021) | Separate consent required for each processing purpose. Cross-border transfer rules enforced. |
opt-in-brazil | Opt-in (Brazil) | LGPD | 10 lawful bases; ANPD-enforced; parental consent gate for under-12. |
general-privacy-consent | General consent | TCF v2.2, COPPA, general privacy notices | Full-flexibility mode: no region-specific behaviours enforced. Configure the banner entirely via your profile. |
notice-only | Notice only | Informational / no opt-in law applies | Consent written automatically as granted for all cookies. Banner shown once as a notice, no action required. |
import type { WidgetCountryResolverFn } from '@consenti/ui'
const geoResolver: WidgetCountryResolverFn = async () => {
const res = await fetch('/api/geo')
const { country } = await res.json()
return country // e.g. 'DE', 'US', 'IN'
}
new ConsentiSetup({
compliance: {
type: 'opt-in',
geoDataProvider: geoResolver,
},
})core
Controls widget behaviour, consent storage, and theming. All keys are optional.
Tenant & locale
| Key | Type | Default | Description |
|---|---|---|---|
tenantId | string | undefined | Logical identifier for this installation. Sent with API requests and used to namespace consent records when multiple Consenti instances share the same backend. |
locale | string | 'en' | BCP 47 locale code, e.g. 'fr', 'fr-CA'. Resolution order: exact match → language prefix → defaultLocale. |
GPC
| Key | Type | Default | Description |
|---|---|---|---|
autoHonorGPC | boolean | 'strict' | false | How to handle the browser's Global Privacy Control signal.false = ignore. true = pre-deny listenGpc cookies and show the GPC banner variant.'strict' = pre-deny and write consent silently — no banner shown. Individual profiles can also set gpcMode per-profile; this field overrides that when explicitly set. |
Profile resolution
| Key | Type | Default | Description |
|---|---|---|---|
usePrebuiltProfiles | boolean | true | When true, Consenti lazy-loads the pre-built profile chunk for the resolved compliance group. Set to false to use only profiles defined via ConsentiProfile or profileOverride. |
cacheResolvedProfiles | boolean | true | When true, the resolved profile URL from the API's /resolve-profile endpoint is cached in sessionStorage with a 1-hour TTL, avoiding redundant network requests on every page load. |
Storage
| Key | Type | Default | Description |
|---|---|---|---|
storage | 'cookie' | 'localStorage' | 'cookie' | Where consent is persisted in the browser.cookie works across subdomains when cookieDomains is set.localStorage is scoped to the exact origin and cannot be shared across subdomains. In API mode the server always issues its own cookie regardless. |
cookieDomains | string | undefined | Comma-separated domain list, e.g. '.example.com,.sub.example.com'. The first entry is used as the Domain attribute on the consent cookie, making it readable on all subdomains of that domain. |
Visitors & receipts
| Key | Type | Default | Description |
|---|---|---|---|
userId | string | undefined | Server-assigned UUID for authenticated users. When set, replaces the browser-generated visitor ID. Combined with API mode, this enables cross-device consent synchronisation — consent granted on mobile is recognised on desktop. |
allowReceipt | boolean | false | When true, a "Download consent receipt" checkbox appears in the preference modal footer. Checking it before saving triggers a JSON download containing a timestamped record of the user's choices. |
CSS & logging
| Key | Type | Default | Description |
|---|---|---|---|
disableCssTemplate | boolean | false | When true, no <style> tag is injected at all. Use this when you provide your own stylesheet and want full control over every rule. All BEM class names still apply. |
console | Array<'error' | 'warn' | 'info' | 'debug'> | ['error'] | Log levels to emit to the browser console. Pass an empty array to suppress all output. Add 'debug' during development to trace profile resolution and consent storage. |
core.theme
Inline CSS token overrides. Each key maps directly to a --consenti-* CSS custom property injected on the widget root element at runtime. You only need to set the values you want to change — unset keys keep their stylesheet default. For full CSS control, see the Themes & CSS guide.
| Key | CSS variable | Default | Description |
|---|---|---|---|
bgColor | --consenti-color-bg | #ffffff | Background colour for banners and modals. |
textColor | --consenti-color-text | #1a1a1a | Primary body text colour. |
primaryColor | --consenti-color-primary | #1565c0 | Primary button background and interactive accent colour. |
primaryTextColor | --consenti-color-primary-text | #ffffff | Text colour rendered on top of primaryColor backgrounds. |
secondaryColor | --consenti-color-secondary | #f0f4f8 | Secondary / ghost button background. |
secondaryTextColor | --consenti-color-secondary-text | #1a3460 | Text colour for secondary buttons. |
borderColor | --consenti-color-border | #e2e8f0 | Borders and dividers. |
accentColor | --consenti-color-accent | #d32f2f | Background for accent-style buttons (destructive actions). |
accentTextColor | --consenti-color-accent-text | #ffffff | Text colour on top of accentColor. |
fontFamily | --consenti-font-family | system-ui, sans-serif | Font stack applied to all widget text. |
fontSizeBase | --consenti-font-size-base | 14px | Base font size for body content. |
fontSizeHeading | --consenti-font-size-heading | inherit | Font size for banner and modal headings. |
fontSizeMultiplier | --consenti-font-size-mult | 1 | Scale multiplier applied to all font sizes. '1.1' = 10% larger throughout. |
borderRadius | --consenti-border-radius | 8px | Border-radius for banner and modal containers. |
buttonBorderRadius | --consenti-border-radius-btn | 4px | Border-radius applied to all button elements. |
toggleBgOn | --consenti-toggle-bg-on | #1565c0 | Background of toggle switches in the ON (granted) state. |
toggleBgOff | --consenti-toggle-bg-off | #cccccc | Background of toggle switches in the OFF (denied) state. |
new ConsentiSetup({
compliance: { type: 'opt-in' },
core: {
theme: {
primaryColor: '#7c3aed', // purple accent
primaryTextColor: '#ffffff',
borderRadius: '12px',
buttonBorderRadius: '999px', // pill buttons
fontFamily: 'Inter, sans-serif',
},
},
})api
Connects the widget to the Consenti backend. When enabled, consent records are posted to the API and the active profile is resolved via /resolve-profile. Disabled by default — the widget works fully offline without it.
| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | When true, the widget calls /resolve-profile to find the best profile for the visitor and posts consent records to the API. Falls back to a pre-built profile if the API request fails. |
baseUrl | string | window.location.origin | Root URL where @consenti/api is mounted. The widget appends /consenti/api/v1/... to this value. Set explicitly when the API is on a different domain. |
authToken | string | '' | Sent as Authorization: Bearer <token> on every API request. Leave empty for public / unauthenticated access. |
tenantId | string | undefined | Tenant identifier sent with API requests. Required when the backend serves multiple tenants. Must match the tenant configured in the dashboard. |
complianceGroup | string | undefined | When set, skips the /resolve-profile auto-resolution call and always fetches the profile for this specific compliance group. Useful for regional deployments or A/B testing. |
trustDomain | boolean | false | Bypasses the domain allowlist check on the resolved profile. Only use during local development or trusted server-side rendering. Never set to true in production. |
new ConsentiSetup({
api: {
enabled: true,
baseUrl: 'https://consent.example.com', // API is on a subdomain
},
// compliance group resolved automatically per-visitor via /resolve-profile
})new ConsentiSetup({
compliance: { type: 'opt-in' },
api: {
enabled: true,
baseUrl: 'https://consent.example.com',
complianceGroup: 'opt-in', // always fetch the GDPR-model profile
},
})api.enabled is true and the network request fails (offline, server error), the widget silently falls back to the pre-built profile for the detected compliance group, then to the built-in default. Consent submission retries are not automatic — use the events API to implement your own retry logic.utils.gtm
Google Tag Manager / Google Consent Mode v2 integration. When a containerId is provided, Consenti pushes consent state to the dataLayer on every consent update.
| Key | Type | Default | Description |
|---|---|---|---|
containerId | string | undefined | GTM container ID, e.g. 'GTM-XXXXXX'. Required to enable any dataLayer pushes. Omit to skip GTM entirely. |
dataLayer | string | 'dataLayer' | Name of the dataLayer array on window. Override only when your site uses a custom variable name (rare). |
events | string[] | [] | Allowlist of Consenti event names to push. An empty array (default) means push all consent events. Pass specific names to filter, e.g. ['consentSubmitted']. |
urlPassthrough | boolean | false | Pushes { url_passthrough: true }to the dataLayer alongside consent events. Enables Google Consent Mode v2 "cookieless pings" — Google can model conversions even when ad_storage is denied. Requires gtag.js on the page. |
adsDataRedaction | boolean | false | Pushes ads_data_redaction: true when ad_storage is denied, causing Google to redact identifying fields from ad pings. Requires gtag.js on the page. |
new ConsentiSetup({
compliance: { type: 'opt-in' },
utils: {
gtm: {
containerId: 'GTM-XXXXXX',
urlPassthrough: true, // cookieless conversion modelling
adsDataRedaction: true, // redact ad pings when consent denied
},
},
})plugins
An array of frontend plugin instances to initialise alongside the widget. Each plugin receives the widget's public API surface via its initialize(widget) method and can hook into consent events, inject DOM, or forward consent signals to third-party services.
import { SegmentPlugin } from '@consenti/ui-plugin-segment'
new ConsentiSetup({
compliance: { type: 'opt-in' },
plugins: [
new SegmentPlugin({ writeKey: 'YOUR_WRITE_KEY' }),
],
})See the Plugins guide for the full plugin API and available first-party plugins.
profileOverride
Accepts a Partial<ResolvedProfile> that is deep-merged on top of the resolved profile after it has been loaded (from the API, a pre-built profile, a local ConsentiProfile, or the built-in default). Only the keys you supply are applied — everything else is left unchanged.
This is a runtime override only. It does not affect the stored profile. See the Profiles guide for a full reference and examples.
// Checkout page — move banner out of the way
new ConsentiSetup({
compliance: { type: 'opt-in' },
profileOverride: {
mainBanner: { position: 'right-bottom' },
},
})new ConsentiSetup({
compliance: { type: 'opt-in' },
profileOverride: {
mainBanner: {
buttons: [
{ text: 'Accept', style: 'primary', action: 'custom', cookies: '*' },
{ text: 'Decline', style: 'secondary', action: 'custom', cookies: '!' },
],
},
},
})rootEl
By default the widget appends a <div id="consenti-root"> to document.body and mounts banners and modals inside it. Use rootEl to mount into your own container instead.
| Value | Behaviour |
|---|---|
string (CSS selector) | Resolved via document.querySelector(). Throws if the element is not found. |
HTMLElement | Used directly. Throws if the element is not attached to the document. |
| omitted | Creates #consenti-root and appends it to document.body (default). |
// HTML: <div id="consent-wrapper"></div>
new ConsentiSetup({
compliance: { type: 'opt-in' },
rootEl: '#consent-wrapper', // CSS selector
// rootEl: document.getElementById('consent-wrapper')!, // or HTMLElement directly
})darkMode
Enables dark colour tokens for the widget. Setting darkMode: true adds the consenti-root--dark class to the root element, which overrides all CSS custom properties to their dark equivalents.
new ConsentiSetup({
compliance: { type: 'opt-in' },
darkMode: true,
})
// Or detect the user's OS preference:
new ConsentiSetup({
compliance: { type: 'opt-in' },
darkMode: window.matchMedia('(prefers-color-scheme: dark)').matches,
})autoInit
By default the widget begins initialising immediately when the constructor runs. Set autoInit: false to prevent this — the widget will not touch the DOM until you explicitly call widget.init().
| Key | Type | Default | Description |
|---|---|---|---|
autoInit | boolean | true | When false, the constructor returns without initialising. Call await widget.init() manually to start the widget. After destroy() you can also call init() again to re-initialise the same instance. |
const widget = new ConsentiSetup({
compliance: { type: 'opt-in' },
rootEl: '#consent-mount',
autoInit: false,
})
// Later, once the mount point exists in the DOM:
await widget.init()
widget.onReady(() => console.log('Ready:', widget.hasConsent()))Minimal configs by use case
Simplest possible — auto-detect compliance
new ConsentiSetup({ })Explicit GDPR opt-in
new ConsentiSetup({ compliance: { type: 'opt-in' } })CCPA opt-out (no banner)
new ConsentiSetup({ compliance: { type: 'opt-out' } })Cross-subdomain consent
new ConsentiSetup({
compliance: { type: 'opt-in' },
core: {
storage: 'cookie',
cookieDomains: '.example.com', // shared across app.example.com, www.example.com, etc.
},
})Authenticated user — cross-device sync
// Server renders the page with the authenticated user's UUID
new ConsentiSetup({
core: {
userId: '{{ server_user_id }}',
},
api: { enabled: true },
})GPC strict mode + GTM
new ConsentiSetup({
compliance: { type: 'opt-in' },
core: {
autoHonorGPC: 'strict', // deny silently, no banner shown
},
utils: {
gtm: { containerId: 'GTM-XXXXXX', adsDataRedaction: true },
},
})TypeScript imports
import type {
ConsentiConfig, // top-level config object
ComplianceWidgetConfig, // compliance section
AgeGateWidgetConfig, // compliance.ageGate section
WidgetCountryResolverFn, // custom geo resolver function type
CoreConfig, // core section
ApiConfig, // api section
UtilsConfig, // utils section
GtmConfig, // utils.gtm section
ThemeConfig, // core.theme section
} from '@consenti/ui'