@growth-labs/consent
v0.5.0
Published
Geography-aware consent management. Banner UI, cookie gating, and a utility other packages use to check consent state. Generic — not analytics-specific.
Readme
@growth-labs/consent
Geography-aware consent management. Banner UI, cookie gating, and a utility other packages use to check consent state. Generic — not analytics-specific.
Config
import consent from '@growth-labs/consent'
consent({
mode: 'geography', // 'required' | 'geography' | 'disabled'
consentCookieName: 'gl_consent',
autoInject: true, // default: package injects the banner script on every page
purposes: {
analytics: { label: 'Analytics', description: 'Helps us understand...', required: false },
marketing: { label: 'Marketing', description: 'Personalized content...', required: false },
},
banner: {
position: 'bottom', // 'bottom' | 'top' | 'center'
showManageLink: true,
defaults: 'auto', // 'auto' | 'unchecked' | 'checked'
privacyPolicyUrl: '/privacy-policy/',
privacyPolicyLabel: 'Privacy policy',
showCloseButton: false, // first-impression banner has no X (see below)
},
// requireConsentRegions defaults to EU/EEA + UK + Brazil
})GDPR-Compliant Defaults
banner.defaults controls the pre-tick behaviour of non-required purpose
checkboxes:
'auto'(default): unchecked when the visitor's country is inrequireConsentRegions(EU/UK/EEA/BR by default), checked otherwise. Per EDPB Guidelines 05/2020 and the CJEU Planet49 ruling, pre-ticked boxes do not constitute valid consent in the EU/UK.'unchecked': always unchecked. Strict opt-in everywhere.'checked': always pre-ticked. Legacy pre-0.5.x behaviour. Only legal in non-GDPR regions.
Required-purpose checkboxes (required: true) always stay checked and
disabled — they are operational, not consent-bearing.
Privacy Policy Link
Setting banner.privacyPolicyUrl renders a link below the action buttons.
Omit the option to suppress the link entirely. EDPB Guidelines 05/2020
§3.3.1 expects informed consent, which the link disclosure supports.
First-Impression Close Button
banner.showCloseButton defaults to false. Closing the first-impression
banner via an "X" dismisses without recording a decision, which both
re-triggers the banner on every subsequent pageview AND fails the EDPB
affirmative-act test. The close affordance is therefore gated to the
manage-preferences sub-view (opened from inside an active consent flow,
where closing is safe).
What It Injects
Middleware: Reads gl_consent cookie → populates context.locals.consent with { analytics: boolean, marketing: boolean, required: boolean }.
Banner: Auto-injected by the integration by default. It shows when consent is required and no cookie exists, supports Accept all / Decline / Manage toggles, and listens for gl:consent_reopen.
Set autoInject: false only when a site intentionally renders the package component itself. The manual component must receive the same runtime values the injected banner uses:
---
import ConsentBanner from '@growth-labs/consent/components/ConsentBanner.astro'
const purposes = [
{ key: 'analytics', label: 'Analytics', description: 'Helps us understand traffic' },
{ key: 'marketing', label: 'Marketing', description: 'Personalized content' },
]
---
<ConsentBanner
consentRequired={Astro.locals.consent?.required ?? false}
cookieName="gl_consent"
purposes={purposes}
position="bottom"
showManageLink={true}
/>Programmatic Reopen
For footer links or privacy pages that should reopen the in-page banner instead of sending users to a separate route:
document.dispatchEvent(
new CustomEvent('gl:consent_reopen', {
detail: { view: 'preferences' }, // or 'main'
}),
)The banner reuses any existing consent cookie to prefill the checkboxes before it opens.
How Geography Detection Works
Uses CF-IPCountry header (available on all Cloudflare Workers requests). If visitor's country is in requireConsentRegions list → consent required. US visitors → consent not required (mode: geography).
Checking Consent in Other Packages
import { isConsentGranted } from '@growth-labs/consent/utils'
if (isConsentGranted(context, 'analytics')) {
// Set tracking cookies, fire analytics
}If @growth-labs/consent is NOT installed, isConsentGranted() always returns true (appropriate for US-only sites).
Integration Order
Consent middleware runs AFTER auth, BEFORE analytics. List order in astro.config.mjs: auth → consent → analytics.
Key Patterns
- Virtual module:
virtual:growth-labs/consent/config - Runtime state self-seeds from the virtual config in middleware, routes, and utils
- Cookie
gl_consentstoresanalytics:true,marketing:false - Banner is minimal HTML — consumers style with CSS/Tailwind
- Client-side control uses
gl:*CustomEvent events ondocument, includinggl:consent_reopen - Zaraz Consent Mode gates GA4 + third-party scripts
