npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vreaab/cm

v0.6.0

Published

CM component library

Readme

@vreaab/cm

npm version License: MIT

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/cm

Peer 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

  1. On page load, all consent parameters are set to 'denied' by default
  2. When the user makes a choice, parameters are updated via gtag('consent', 'update', {...})
  3. 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 dev

Build

npm run build

Project 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 styles

License

MIT License - see LICENSE for details.

Copyright (c) 2025 Jonathan Yngfors