Skip to main content

Cookies Consent Library

The @lumio/cookies-consent shared package provides a GDPR-compliant cookie-consent UX for Lumio's external frontend apps (apps/web and apps/id).

It wraps a provider, banner, preferences modal, floating settings button, and detection hooks, with one additive feature (exemptPathPrefixes) to silence the banner on OBS overlay routes where a consent dialog would be inappropriate.

When to use

  • Wrap your app's root layout with <ConsentProvider>.
  • The provider renders <ConsentBanner /> and <CookieSettingsButton /> automatically (controlled by showFloatingButton — default true).
  • Use useConsent() from any client component to gate loading of non-essential scripts / cookies.

Consumer apps must mount <TooltipProvider />

The CookieSettingsButton floating tooltip uses Lumio's shared <TooltipProvider /> (from @lumio/ui) which reads data-tooltip / data-tooltip-pos attributes. Mount the provider once at your app's root layout.

data-tooltip-pos (top | bottom | left | right, default top) is a preference, not a hard constraint: if the tooltip would clip past the viewport on the requested side it flips to the opposite side, and its cross-axis position is clamped to the viewport edge instead of being cut off. This keeps tooltips readable when the anchor sits in the top row of a popout or hard against a viewport edge.

Public API

SymbolKindPurpose
ConsentProviderComponentWraps the app, loads/persists state, renders banner + floating button.
useConsent()HookReturns hasConsented, isAllowed(category), consent, openPreferences, isExempt. (openPreferences is the context's openBanner.)
useConsentContext()HookThe full context value, including acceptAll / acceptEssentialOnly / closeBanner. useConsent() is the narrowed, preferred surface.
useCookiePreferences, useCookieStatusHookPreferences-modal state and per-category status.
useCookieDetector, useStorageDetector, useBrowserDataDetectorHookReactive wrappers over the detection utils below.
ConsentBanner, CookieSettingsButton, CookieStatusIndicatorComponentStandalone UI pieces if you need them outside the provider's auto-render.
ConditionalScript, ConditionalContent, EssentialScriptComponentOnly mount <script>-like content when a category is allowed.
GoogleAnalytics, FacebookPixel, Hotjar, Matomo, PlausibleComponentPre-wired analytics integrations that respect consent.
defaultTranslationsData{ en, de } default copy.
getStoredConsent, setStoredConsent, clearStoredConsentUtilLow-level localStorage access.
getDetectedCookies, getCookiesByCategory, getCookiesByService, hasServiceCookies, getCookieSummaryUtilRuntime cookie detection (drives the preferences modal).
getDetectedStorage, getDetectedLocalStorage, getDetectedSessionStorage, getStorageByCategory, getStorageByType, getStorageByService, hasServiceStorage, getStorageSummaryUtilSame for localStorage / sessionStorage.

Integration status

The library is mounted in two consumer apps:

AppMounted inappNameexemptPathPrefixesPolicy URL
apps/websrc/app/(main)/layout.tsx"Web"["/overlay", "/popout", "/api", "/dashboard", "/account", "/hub"]/legal/cookies (relative)
apps/idsrc/app/layout.tsx"ID"none${WEB_URL}/legal/cookies

Each app mounts <TooltipProvider /> next to <ConsentProvider> inside NextIntlClientProvider. The provider renders the banner and floating settings button internally — consumers do NOT pass them as children. apps/web additionally nests <SentryConsentSync /> inside the provider so Sentry's capture follows the consent state.

The webapp exempts the whole authenticated surface (/dashboard, /account, /hub) in addition to the embed routes, so the banner only appears on the public marketing and legal pages.

apps/admin does NOT integrate the consent library. Admin is an internal, auth-gated tool for the zaflun team — no external end-users reach it, so no GDPR consent prompt is required.

Bot apps and apps/docs do not integrate the library either (server-only / Docusaurus).

exemptPathPrefixes

Pass this prop to the provider to suppress the consent UI on specific pathname prefixes (e.g. OBS browser-source routes at /overlay/* where a banner would break the embed).

On matching paths:

  • <ConsentBanner /> and <CookieSettingsButton /> do not render.
  • useConsent().isExempt === true.
  • isAllowed("essential") returns true; all other categories return false.
  • acceptAll / acceptEssentialOnly / openPreferences / openBanner / closeBanner are no-ops.
<ConsentProvider
translations={defaultTranslations[locale]}
policyUrl="/legal/cookies"
appName="Web"
exemptPathPrefixes={["/overlay", "/api"]}
footerLinks={{ privacyUrl: "/legal/privacy", imprintUrl: "/legal/imprint" }}
>
{children}
</ConsentProvider>

Edit shared/cookies-consent/src/utils/cookieDetector.ts. Append to the cookiePatterns array:

{ pattern: /^your_cookie_name$/, category: "functional", service: "Your Service" }

An optional devOnly: true marks a cookie that only appears in local development. A name matching no pattern falls through to category: "unknown".

Also add a matching entry to shared/cookies-consent/src/i18n/en.json (and de.json) under the appropriate category's cookies array so the preferences modal shows the human-friendly name + purpose + duration.

Storage key

Persistent state is stored under localStorage["lumio_cookie_consent"] as { version, ... }. getStoredConsent() returns ConsentState | null and clears the entry when the stored version does not match the library's current STORAGE_VERSION — a version bump therefore re-prompts every user.

Categories

essential, functional, statistics, marketing (CookieCategory in shared/cookies-consent/src/types/consent.ts) — semantics match the spec at docs/superpowers/specs/2026-04-16-cookies-consent-library-design.md, with the app integration described in 2026-04-17-cookies-consent-integration-design.md.