Skip to content

FRD: Consent and Cookies Library

Document Summary

FieldDetails
Feature NameConsent / Cookies Library
StatusDraft
OwnerDavid Holmes
ContributorsEngineering, Legal
Target Releasev2.0.0 (P2)
Related LinksRoadmap item #74
Last Updated2026-05-26

Introduction

Overview

The Consent / Cookies library provides a complete consent management solution: a banner UI component, a preference model, analytics gating logic, and a persistence layer. Applications integrate it to collect, store, and enforce user consent choices in compliance with GDPR, CCPA, and similar regulations. All analytics and tracking scripts are gated on consent state, preventing execution until the user grants permission.

Goals

  • Provide a composable consent banner component with accept-all, reject-all, and customize options.
  • Model consent preferences as a typed, serializable structure with granular categories (necessary, analytics, marketing, functional).
  • Gate analytics and tracking script loading on consent state via a programmatic API.
  • Persist consent choices in cookies with configurable expiration and domain scope.
  • Support server-side consent checks for SSR applications.

Non-Goals

  • Implementing a full Consent Management Platform (CMP) with vendor tag management.
  • Providing legal advice or jurisdiction-specific consent text.
  • Managing individual cookie deletion (browser limitation).
  • Building an admin dashboard for consent analytics.

Scope

In Scope

AreaDescription
Banner componentAccessible, dismissible consent banner with accept/reject/customize actions
Preference centerModal or panel UI for granular category-level consent choices
Consent modelTyped ConsentPreferences with necessary, analytics, marketing, functional categories
PersistenceCookie-based storage with configurable TTL and domain
Analytics gatingisConsentGranted(category) function and ConsentGate component for conditional rendering
SSR supportServer-side consent check from request cookies
React hookuseConsent() hook for reading and updating consent state
Mock/test utilitiesHelpers for setting consent state in tests
Unit testsCoverage of model, persistence, gating, and component behavior
DocumentationStorybook MDX docs with usage examples

Out of Scope

AreaReason
Jurisdiction-specific legal textLegal team provides copy; library provides slots
Tag management (GTM integration)Consumer responsibility; library provides the gating signal
Cookie scanning / auditSeparate tooling concern
Consent analytics dashboardBackend concern outside library scope
Geo-detection for consent requirementsApplication-level concern

Users and Pain Points

User Groups

UserDescriptionNeeds
Application developersEngineers integrating consent managementA drop-in banner and gating API that works with minimal configuration
Legal / complianceTeam members ensuring regulatory complianceConfidence that scripts are gated on consent and preferences are persisted correctly
End usersWebsite visitorsA clear, accessible way to manage cookie preferences
QA engineersTesters verifying consent flowsUtilities for setting consent state in tests without UI interaction

Pain Points

UserPain PointImpact
Application developersEach app builds its own consent banner with inconsistent behaviorDuplicated effort; inconsistent UX across products
Application developersAnalytics scripts load before consent is grantedCompliance risk; potential regulatory fines
Legal / complianceNo standardized preference model; each app stores consent differentlyDifficult to audit; hard to prove compliance
End usersConsent banners are often inaccessible or confusingPoor UX; users cannot effectively manage preferences

Definitions

TermDefinition
Consent categoryA logical grouping of cookies/scripts: necessary (always allowed), analytics, marketing, functional
Consent bannerThe initial UI prompting the user to accept, reject, or customize cookie preferences
Preference centerA detailed UI showing all consent categories with individual toggles
Analytics gatingThe mechanism that prevents analytics/tracking scripts from loading until consent is granted
Consent cookieThe cookie storing the user’s serialized consent preferences

Current State

Existing Behavior

No shared consent management solution exists. Each application implements its own banner, storage, and gating logic.

Current Limitations

  • No standardized consent preference model across applications.
  • No shared banner component; each app builds its own with varying accessibility quality.
  • Analytics scripts often load unconditionally or rely on ad-hoc gating.
  • No SSR-compatible consent check; server-rendered pages cannot adapt to consent state.

Existing Workarounds

  • Developers copy consent banner implementations between projects.
  • Some apps use third-party CMP tools (OneTrust, CookieBot) but these are heavyweight and inconsistently configured.
  • Analytics gating is done with manual if checks scattered through initialization code.

Proposed Solution

Summary

Ship a TypeScript + React library (@dmwd/consent) providing a ConsentBanner component, a PreferenceCenter component, a ConsentProvider context, a useConsent() hook, cookie-based persistence, and an isConsentGranted() gating function.

Key Capabilities

  • ConsentBanner component with accept-all, reject-all, and customize buttons.
  • PreferenceCenter component with per-category toggles and save/cancel actions.
  • ConsentProvider wrapping the app to provide consent context.
  • useConsent() hook returning current preferences and update functions.
  • isConsentGranted(category) for programmatic checks (works on server and client).
  • ConsentGate component for conditionally rendering children based on consent.
  • Cookie persistence with configurable name, TTL, domain, and SameSite attribute.

User Experience

On first visit, users see a consent banner at the bottom of the viewport. They can accept all, reject all (keeping only necessary cookies), or open the preference center to choose categories individually. Their choice persists across sessions. Returning users see no banner unless consent has expired.

Developer Experience

Developers wrap their app in ConsentProvider, place ConsentBanner in their layout, and wrap analytics scripts in ConsentGate category="analytics". The useConsent() hook provides the current state for custom logic.


Requirements

IDRequirementPriorityNotes
FR-001The library must export a ConsentBanner componentMustPrimary UI
FR-002The library must export a PreferenceCenter componentMustGranular control
FR-003The library must export a ConsentProvider contextMustState management
FR-004The library must export a useConsent() hookMustProgrammatic access
FR-005The library must export an isConsentGranted(category) functionMustGating logic
FR-006The library must export a ConsentGate componentMustDeclarative gating
FR-007Consent must persist in cookies with configurable TTLMustGDPR requires re-consent
FR-008The necessary category must always be enabled and not toggleableMustLegal requirement

Priority Definitions

PriorityMeaning
MustRequired for this feature to ship.
ShouldImportant, but can be deferred if needed.
CouldNice to have. Not required for initial release.

Functional Requirements

IDRequirementUser BenefitPriority
FUNC-001ConsentBanner renders with accept-all, reject-all, and customize buttonsUsers have clear choicesMust
FUNC-002Clicking accept-all grants all categories and dismisses the bannerQuick opt-inMust
FUNC-003Clicking reject-all grants only necessary and dismisses the bannerQuick opt-outMust
FUNC-004Clicking customize opens the PreferenceCenterGranular controlMust
FUNC-005PreferenceCenter shows a toggle for each non-necessary categoryUsers can choose individuallyMust
FUNC-006PreferenceCenter has save and cancel actionsUsers confirm or discard changesMust
FUNC-007ConsentGate category="analytics" renders children only when analytics consent is grantedScripts are gated on consentMust
FUNC-008useConsent() returns { preferences, grantAll, rejectAll, updateCategory, hasConsented }Programmatic access to consent stateMust
FUNC-009Banner does not render when user has already consented and consent has not expiredReturning users are not re-promptedMust
FUNC-010isConsentGranted(category, cookieString?) works server-side by parsing a cookie stringSSR supportShould

Non-Functional Requirements

IDRequirementCategoryPriority
NFR-001Banner and preference center must meet WCAG 2.1 AAAccessibilityMust
NFR-002Consent cookie must use Secure, SameSite=Lax, and HttpOnly when possibleSecurityMust
NFR-003Banner must not cause layout shift (CLS)PerformanceMust
NFR-004Library must work with React 18+ and React 19CompatibilityMust
NFR-005Bundle size under 8 KB minified + gzipped (excluding React)PerformanceShould
NFR-006Consent state must be available within 1 render cycle of app mountPerformanceMust

API / Interface Requirements

Public API

NameTypeDescriptionRequired
ConsentProvidercomponentContext provider; accepts config prop with cookie settingsYes
ConsentBannercomponentBanner UI; accepts privacyPolicyUrl, customizeLabel, slot props for textYes
PreferenceCentercomponentCategory toggles; accepts categories override for custom labelsYes
ConsentGatecomponent{ category, children, fallback? } — renders children when consent grantedYes
useConsenthookReturns { preferences, grantAll, rejectAll, updateCategory, hasConsented, resetConsent }Yes
isConsentGrantedfunction(category: ConsentCategory, cookieString?: string) => booleanYes
ConsentPreferencestype{ necessary: true; analytics: boolean; marketing: boolean; functional: boolean; consentedAt: string }Yes
ConsentConfigtype{ cookieName?: string; ttlDays?: number; domain?: string; sameSite?: string }Yes

Example Usage

import { ConsentProvider, ConsentBanner, ConsentGate } from "@dmwd/consent";
function App() {
return (
<ConsentProvider config={{ ttlDays: 365, domain: ".example.com" }}>
<Layout>
<ConsentGate category="analytics">
<AnalyticsScript />
</ConsentGate>
<ConsentBanner privacyPolicyUrl="/privacy" />
</Layout>
</ConsentProvider>
);
}

API Notes

  • ConsentPreferences.necessary is always true and cannot be set to false.
  • ConsentGate renders fallback (default: null) when consent is not granted.
  • resetConsent() clears the cookie and re-shows the banner.
  • Cookie value is JSON-serialized ConsentPreferences, base64-encoded.

Accessibility Requirements

IDRequirementNotes
A11Y-001Banner must be a role="dialog" with aria-labelAnnounced to screen readers
A11Y-002Banner must trap focus when openPrevents interaction with content behind
A11Y-003All interactive elements must be keyboard accessibleTab, Enter, Space, Escape
A11Y-004Escape key dismisses the preference center (not the banner)Standard dialog behavior
A11Y-005Toggle switches must have accessible labels including category nameScreen readers announce what each toggle controls
A11Y-006Color contrast must meet WCAG 2.1 AA (4.5:1 for text)All text and interactive elements
A11Y-007Reduced motion: no entry/exit animations when prefers-reduced-motion is setRespects user preference

Checklist

  • Keyboard support is defined.
  • Focus behavior is defined.
  • Screen reader behavior is defined.
  • Color contrast requirements are met.
  • Reduced motion behavior is considered.
  • Semantic HTML expectations are documented.
  • ARIA usage is defined only where needed.

Content and Documentation Requirements

IDRequirementLocationPriority
DOC-001API reference with all exported components, hooks, and typesStorybook MDXMust
DOC-002Quick start guide showing ConsentProvider + ConsentBanner setupStorybook MDXMust
DOC-003Analytics gating guide with ConsentGate examplesStorybook MDXMust
DOC-004SSR guide showing isConsentGranted with cookie parsingStorybook MDXShould
DOC-005Testing guide with consent state utilitiesStorybook MDXMust
DOC-006Customization guide for banner text, styling, and categoriesStorybook MDXShould

Documentation Should Include

  • Overview and compliance context
  • Quick start setup
  • When to use (and when a full CMP is more appropriate)
  • Banner customization (text slots, styling)
  • Analytics gating patterns
  • SSR integration
  • Testing utilities
  • API reference
  • Common mistakes (e.g., loading scripts outside ConsentGate)

Dependencies

DependencyTypeOwnerStatusNotes
React 18+EngineeringDavid HolmesReadyPeer dependency
Design system tokensDesignDavid HolmesReadyColors, spacing, typography
None (other runtime)ReadyCookie persistence is self-contained

Risks and Tradeoffs

Risk / TradeoffImpactMitigation
Library does not provide legal textConsumers must supply their own consent copyProvide clear slots and example text in docs; document that legal review is required
Cookie-only persistence may not work with strict browser cookie policiesSome browsers block third-party cookiesUse first-party cookie with configurable domain; document limitations
Four fixed categories may not fit all appsSome apps need more or fewer categoriesAllow category override via config; default to the four standard categories
CLS from banner renderingBanner appearing late causes layout shiftRender banner position in CSS before JS hydration; use fixed positioning
No geo-detectionApps in single-jurisdiction deployments show banner unnecessarilyLibrary does not own geo-detection; consumer wraps ConsentProvider conditionally

Open Questions

IDQuestionOwnerStatusResolution
Q-001Should the library support localStorage as an alternative to cookies for SPAs that do not need SSR consent checks?David HolmesOpen
Q-002Should we define a ConsentEvent type for analytics tracking of consent actions (granted, rejected, customized)?David HolmesOpen
Q-003Should the preference center support custom categories beyond the default four?David HolmesOpen
Q-004Should the banner support a “powered by” slot for CMP vendor attribution?David HolmesOpen

Acceptance Criteria

IDCriteriaRelated Requirement
AC-001ConsentBanner renders with accept-all, reject-all, and customize buttonsFUNC-001
AC-002Clicking accept-all sets all categories to true and stores a consent cookieFUNC-002
AC-003Clicking reject-all sets only necessary: true and stores a consent cookieFUNC-003
AC-004ConsentGate category="analytics" renders children only when analytics consent is grantedFUNC-007
AC-005ConsentGate renders fallback when consent is not grantedFUNC-007
AC-006Banner does not render when a valid, non-expired consent cookie existsFUNC-009
AC-007isConsentGranted("analytics", cookieString) returns correct boolean from a cookie stringFUNC-010
AC-008Banner has role="dialog" and traps focusA11Y-001, A11Y-002
AC-009All interactive elements are keyboard accessibleA11Y-003
AC-010Unit tests pass covering all consent flowsFR-001 through FR-008
AC-011Storybook MDX docs render without errorsDOC-001

LLM Handoff Instructions

Expected LLM Behavior

  • Follow the requirements and acceptance criteria in this document.
  • Do not expand scope beyond the In Scope section.
  • Respect the Out of Scope section.
  • Ask for clarification only when a requirement cannot be safely interpreted.
  • Prefer existing design system components and tokens over custom styles.
  • Banner should use design system color tokens, not hardcoded colors.
  • Use ConsentPreferences as a discriminated type with necessary: true as a literal.
  • Test consent cookie serialization and deserialization round-trips.

LLM Should Not

  • Invent undocumented product behavior.
  • Provide legal advice or jurisdiction-specific consent text.
  • Add third-party cookie management libraries.
  • Change unrelated components.
  • Use document.cookie directly outside the persistence module.
  • Implement geo-detection or jurisdiction routing.

Decision Log

DateDecisionReasonOwner
2026-05-26Four default consent categories (necessary, analytics, marketing, functional)Covers the most common GDPR/CCPA categorization; extensible via configDavid Holmes
2026-05-26Cookie-based persistence (not localStorage)Enables SSR consent checks; first-party cookies are widely supportedDavid Holmes
2026-05-26Fixed positioning for banner to avoid CLSLayout shift from a top/bottom banner is a common performance issueDavid Holmes

Document History

DateAuthorChange
2026-05-26David HolmesInitial draft