@vreaab/cm
v0.6.0
Published
CM component library
Maintainers
Readme
@vreaab/cm
A GDPR-compliant React consent management library with Google Consent Mode V2 integration.
Why @vreaab/cm?
- GDPR Compliance - Equal button prominence for Accept/Reject options, no dark patterns
- Google Analytics 4 Integration - Automatic Consent Mode V2 parameter updates
- Safari Private Mode Support - Fallback storage adapter using cookies when localStorage is unavailable
- Tri-State Consent Model - Distinguishes between granted, denied, and unset states
- Internationalization Ready - Full i18n support via react-intl
Features
- Consent Banner for first-time visitors
- Preferences Modal for granular control
- Floating Cookie Trigger for returning users
- Google Consent Mode V2 integration
- Revocation detection with auto-reload countdown
- Storage adapter with localStorage + cookie fallback
- Full TypeScript support
- SSR/Next.js compatible with hydration safety
Installation
npm install @vreaab/cmPeer Dependencies
- React 18 or 19
- React DOM 18 or 19
Quick Start
Wrap your application with ConsentProvider and add the consent components:
import {
ConsentProvider,
ConsentBanner,
ConsentPreferencesModal,
ConsentTrigger,
} from '@vreaab/cm';
function App() {
return (
<ConsentProvider>
{/* Your app content */}
<main>...</main>
{/* Consent UI components */}
<ConsentBanner />
<ConsentPreferencesModal />
<ConsentTrigger />
</ConsentProvider>
);
}API Reference
ConsentProvider
The root provider component that manages consent state.
<ConsentProvider
locale="en" // Optional: Locale for i18n (default: 'en')
messages={messages} // Optional: Custom translations
>
{children}
</ConsentProvider>| Prop | Type | Default | Description |
|------|------|---------|-------------|
| locale | string | 'en' | Locale code for internationalization |
| messages | Record<string, string> | {} | Custom message translations keyed by message ID |
| children | ReactNode | Required | Application content |
ConsentBanner
Displays automatically for new visitors. Hides after user makes a choice.
<ConsentBanner />No props required. Visibility is controlled by consent state.
ConsentPreferencesModal
Detailed preferences dialog for granular category control.
<ConsentPreferencesModal />No props required. Open/close state is managed by the context.
ConsentTrigger
Floating icon button for returning users to access preferences.
<ConsentTrigger />No props required. Appears only after user has made an initial choice.
useConsent Hook
Access consent state and actions from any component.
const {
// State
consentState, // Current consent for all categories
hasInteracted, // Whether user has made a choice
isLoading, // True during initial load
// Actions
updateConsent, // Update a single category
acceptAll, // Accept all categories
rejectOptional, // Accept only necessary
openPreferences, // Show preferences modal
// Modal State
isPreferencesOpen,
setIsPreferencesOpen,
// Notifications
showToast, // Display a toast message
} = useConsent();Example: Conditional Content
function AnalyticsSection() {
const { consentState } = useConsent();
if (consentState.analytics !== true) {
return <p>Enable analytics cookies to see usage statistics.</p>;
}
return <AnalyticsDashboard />;
}Consent Categories
| Category | Required | Description | Google Consent Mode Parameters |
|----------|----------|-------------|-------------------------------|
| necessary | Yes | Essential cookies for site functionality | None (always allowed) |
| analytics | No | Usage tracking and statistics | analytics_storage |
| marketing | No | Advertising and remarketing | ad_storage, ad_user_data, ad_personalization |
| preferences | No | User settings and personalization | functionality_storage |
Tri-State Values
Each category uses a tri-state model:
| Value | Meaning |
|-------|---------|
| true | User explicitly granted consent |
| false | User explicitly denied consent |
| null | User has not made a choice yet |
Google Analytics Integration
Script Setup
Add the Google Analytics script to your HTML head. The library automatically manages consent parameters.
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>How It Works
- On page load, all consent parameters are set to
'denied'by default - When the user makes a choice, parameters are updated via
gtag('consent', 'update', {...}) - Google Analytics respects these parameters automatically
Conditional Script Loading
For stricter privacy, load analytics scripts only after consent:
function AnalyticsScript() {
const { consentState } = useConsent();
useEffect(() => {
if (consentState.analytics === true) {
// Load GA script dynamically
const script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
script.async = true;
document.head.appendChild(script);
}
}, [consentState.analytics]);
return null;
}Theming
The library uses CSS custom properties for theming, scoped under the [data-cm] selector to avoid conflicts with your application's styles.
Customizing Colors
Override the default theme by targeting the [data-cm] selector:
/* Light mode customization */
[data-cm] {
--cm-background: #ffffff;
--cm-foreground: #1a1a1a;
--cm-primary: #3b82f6;
--cm-primary-foreground: #ffffff;
--cm-muted: #f5f5f5;
--cm-muted-foreground: #737373;
--cm-border: #e5e5e5;
}
/* Dark mode customization */
.dark [data-cm],
[data-cm].dark {
--cm-background: #1a1a1a;
--cm-foreground: #fafafa;
--cm-primary: #60a5fa;
--cm-primary-foreground: #1a1a1a;
--cm-muted: #262626;
--cm-muted-foreground: #a3a3a3;
--cm-border: #404040;
}Available CSS Variables
| Variable | Description |
|----------|-------------|
| --cm-background | Main background color |
| --cm-foreground | Main text color |
| --cm-primary | Primary action color (buttons) |
| --cm-primary-foreground | Text on primary color |
| --cm-secondary | Secondary action color |
| --cm-secondary-foreground | Text on secondary color |
| --cm-muted | Muted/subtle background |
| --cm-muted-foreground | Muted text color |
| --cm-accent | Accent/hover color |
| --cm-accent-foreground | Text on accent color |
| --cm-destructive | Destructive action color |
| --cm-border | Border color |
| --cm-input | Input field border/background |
| --cm-ring | Focus ring color |
| --cm-radius | Border radius base value |
CSS Isolation
All library styles are:
- Prefixed with
cm:to avoid class name conflicts - Important to override conflicting host styles
- Scoped via CSS variables under
[data-cm]
This ensures the consent UI renders correctly regardless of your application's CSS framework or global styles.
Internationalization
Using a Different Locale
<ConsentProvider locale="sv">
{/* Swedish locale */}
</ConsentProvider>Providing Custom Translations
const swedishMessages = {
'consent.banner.title': 'Cookiemedgivande',
'consent.banner.description': 'Vi använder cookies för att förbättra din upplevelse...',
'consent.actions.acceptAll': 'Acceptera alla',
'consent.actions.rejectOptional': 'Avvisa valfria',
'consent.actions.customize': 'Anpassa',
// ... more translations
};
<ConsentProvider locale="sv" messages={swedishMessages}>
{children}
</ConsentProvider>Message ID Reference
| Message ID | Default Text |
|------------|--------------|
| consent.banner.title | Cookie Consent |
| consent.banner.description | We use cookies to enhance your experience... |
| consent.actions.acceptAll | Accept All |
| consent.actions.rejectOptional | Reject Optional |
| consent.actions.customize | Customize |
| consent.actions.learnMore | Learn more |
| consent.modal.title | Cookie Preferences |
| consent.modal.description | Manage your cookie preferences... |
| consent.actions.save | Save Preferences |
| consent.actions.cancel | Cancel |
| consent.category.required | Required |
| consent.revocation.title | Page Reload Required |
| consent.revocation.description | You have disabled some cookies... |
| consent.revocation.countdown | Reloading in {seconds}s... |
| consent.actions.reloadNow | Reload Now |
| consent.unsavedChanges.title | Unsaved Changes |
| consent.unsavedChanges.description | You have unsaved changes... |
| consent.actions.discardChanges | Discard Changes |
| consent.actions.keepEditing | Keep Editing |
| consent.feedback.saved | Your cookie preferences have been saved. |
| consent.trigger.label | Manage cookie preferences |
| consent.category.necessary.label | Necessary |
| consent.category.necessary.description | Essential for the website to function... |
| consent.category.analytics.label | Analytics |
| consent.category.analytics.description | Help us understand how visitors use... |
| consent.category.marketing.label | Marketing |
| consent.category.marketing.description | Used to track visitors across websites... |
| consent.category.preferences.label | Preferences |
| consent.category.preferences.description | Remember your settings and preferences... |
TypeScript Support
Full type definitions are included. Key exports:
import type {
ConsentCategory, // 'necessary' | 'analytics' | 'marketing' | 'preferences'
ConsentValue, // true | false | null
ConsentState, // Record of all category values
ConsentCategoryConfig // Category metadata with gtag mappings
} from '@vreaab/cm';Type Definitions
// Consent value: tri-state model
type ConsentValue = true | false | null;
// Available categories
type ConsentCategory = 'necessary' | 'analytics' | 'marketing' | 'preferences';
// Full consent state
interface ConsentState {
necessary: ConsentValue;
analytics: ConsentValue;
marketing: ConsentValue;
preferences: ConsentValue;
}
// Category configuration
interface ConsentCategoryConfig {
id: ConsentCategory;
label: string;
description: string;
required: boolean;
gtagParams: string[];
}Browser Compatibility
| Browser | Version | Notes | |---------|---------|-------| | Chrome | 90+ | Full support | | Firefox | 88+ | Full support | | Safari | 14+ | localStorage fallback to cookies in private mode | | Edge | 90+ | Full support |
Storage Keys
Consent data is stored with versioned keys to support future migrations:
| Key | Description |
|-----|-------------|
| cm_v1_hasInteracted | Whether user has made any choice |
| cm_v1_consent_{category} | Consent value per category |
| cm_v1_timestamp | ISO timestamp of last consent update |
Contributing
Development Setup
git clone https://github.com/vrea/cm.git
cd cm
npm install
npm run devBuild
npm run buildProject Structure
src/
├── components/consent/ # React UI components
│ ├── ConsentBanner.tsx
│ ├── ConsentPreferencesModal.tsx
│ ├── ConsentProvider.tsx
│ └── ConsentTrigger.tsx
├── lib/
│ ├── consent/ # Core logic
│ │ ├── consent-engine.ts
│ │ ├── gtag-integration.ts
│ │ ├── messages.ts
│ │ ├── storage-adapter.ts
│ │ └── types.ts
│ └── react/ # React exports
│ └── index.ts
└── globals.css # Tailwind stylesLicense
MIT License - see LICENSE for details.
Copyright (c) 2025 Jonathan Yngfors
