Components

ConsentManagerProvider

ConsentManagerProvider is the root component for the c15t consent system. It initializes the consent store, detects the user's jurisdiction, resolves translations, and provides consent state to all child components via React context.

Every other c15t component and hook must be rendered inside this provider.

Basic Usage

import { type ReactNode } from 'react';
import { ConsentManagerProvider, ConsentBanner, ConsentDialog } from '@c15t/nextjs';

export default function ConsentManager({ children }: { children: ReactNode }) {
  return (
    <ConsentManagerProvider
      options={{
        mode: 'hosted',
        backendURL: '/api/c15t',
        consentCategories: ['necessary', 'measurement', 'marketing'],
      }}
    >
      <ConsentBanner />
      <ConsentDialog />
      {children}
    </ConsentManagerProvider>
  );
}

Options Reference

Warning: ExtractedTypeTable: Could not extract "CommonInlineStoreOptions" from "./packages/ui/src/theme/options.ts" using base path "/vercel/path0/apps/c15t-docs/.leadtype/c15t". Verify the path/name and that the file is included by your tsconfig.

Loading…

Warning: ExtractedTypeTable: Could not extract "ConsentManagerContentOptions" from "./packages/ui/src/theme/options.ts" using base path "/vercel/path0/apps/c15t-docs/.leadtype/c15t". Verify the path/name and that the file is included by your tsconfig.

Loading…

Warning: ExtractedTypeTable: Could not extract "UIOptions" from "./packages/ui/src/theme/types.ts" using base path "/vercel/path0/apps/c15t-docs/.leadtype/c15t". Verify the path/name and that the file is included by your tsconfig.

Loading…

Mode: hosted vs offline

// Hosted mode — persists to hosted backend
<ConsentManagerProvider
  options={{
    mode: 'hosted',
    backendURL: '/api/c15t',
  }}
>

// Offline mode — local cookie storage only
<ConsentManagerProvider
  options={{
    mode: 'offline',
  }}
>

See Client Modes for a detailed comparison.

Content Security Policy

c15t injects a <style id="c15t-theme"> element for your theme tokens, and the script loader injects a <script> element per consented vendor. Under a nonce-based Content Security Policy, both are blocked unless they carry your nonce.

Pass it once through the nonce option and c15t applies it to everything it injects:

<ConsentManagerProvider
  options={{
    mode: 'offline',
    nonce: yourRequestNonce,
  }}
>
  {children}
</ConsentManagerProvider>

A nonce set on an individual script definition still wins, so you can override a single vendor without changing the provider.

Info

Browsers hide the nonce content attribute once a policy is active. Inspecting the element shows no nonce="", but element.nonce still returns the value — this is expected and not a sign that c15t dropped it.

Inline style attributes

The nonce option covers the elements c15t injects. It cannot cover inline style="..." attributes, which several components rely on — a nonce never authorizes a style attribute, because nonces apply to elements only.

Style attributes are governed by style-src-attr, and when that directive is absent CSP falls back to style-src. A nonce-based style policy therefore blocks them:

style-src 'self' 'nonce-abc123';

To keep the nonce requirement for stylesheets while still allowing style attributes, set style-src-attr explicitly:

style-src 'self' 'nonce-abc123';
style-src-attr 'unsafe-inline';

Reading the nonce in the App Router

Next.js does not expose the request nonce to client components, so read it in a server component and pass it down. Generate the nonce in middleware, forward it on a request header, then hand it to the provider from your layout:

middleware.ts
import { NextResponse, type NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const nonce = crypto.randomUUID();
  const isDev = process.env.NODE_ENV === 'development';

  const csp = [
    `default-src 'self'`,
    // Next.js dev tooling (React Refresh and HMR) requires 'unsafe-eval'.
    `script-src 'self' 'nonce-${nonce}'${isDev ? " 'unsafe-eval'" : ''}`,
    `style-src 'self' 'nonce-${nonce}'`,
    // Nonces cannot authorize inline style attributes — see above.
    `style-src-attr 'unsafe-inline'`,
  ].join('; ');

  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-nonce', nonce);
  requestHeaders.set('Content-Security-Policy', csp);

  const response = NextResponse.next({ request: { headers: requestHeaders } });
  response.headers.set('Content-Security-Policy', csp);

  return response;
}
app/layout.tsx
import { headers } from 'next/headers';
import { ConsentManagerProvider } from '@c15t/nextjs';

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const nonce = (await headers()).get('x-nonce') ?? undefined;

  return (
    <html lang="en">
      <body>
        <ConsentManagerProvider options={{ mode: 'offline', nonce }}>
          {children}
        </ConsentManagerProvider>
      </body>
    </html>
  );
}

legalLinks defines the URLs shown in consent UI text (banner, dialog, and widget where applicable). Configure only the links you want to expose.

<ConsentManagerProvider
  options={{
    backendURL: 'https://your-instance.c15t.dev',
    legalLinks: {
      privacyPolicy: {
        href: '/privacy',
        target: '_self',
      },
      cookiePolicy: {
        href: '/cookies',
        target: '_self',
      },
      termsOfService: {
        href: 'https://example.com/terms',
        target: '_blank',
        rel: 'noopener noreferrer',
        label: 'Terms of Service',
      },
    },
  }}
>

Notes:

  • Omitting a key (for example termsOfService) hides that link.
  • label overrides the translated text for that single link.
  • Use _self for internal pages and _blank + rel="noopener noreferrer" for external pages.
  • Control which of the configured links render in each component via the component's legalLinks prop.

Overrides

overrides lets you force location/language signals instead of browser or network detection. This is useful for QA, local development, and preview environments.

<ConsentManagerProvider
  options={{
    backendURL: 'https://your-instance.c15t.dev',
    overrides: {
      country: 'DE',
      region: 'BY',
      language: 'de-DE',
    },
  }}
>

You can also override Global Privacy Control (GPC) behavior during testing:

<ConsentManagerProvider
  options={{
    backendURL: 'https://your-instance.c15t.dev',
    overrides: {
      gpc: true,
    },
  }}
>

Policy Packs

In hosted mode (recommended), the backend resolves the correct policy automatically — no frontend policy config needed:

<ConsentManagerProvider
  options={{
    backendURL: 'https://your-instance.c15t.dev',
  }}
>

Fallback: Offline Policies

When no backend is available, ConsentManagerProvider accepts offlinePolicy.policyPacks for local policy resolution during development, testing, previews, or temporary backend outages:

<ConsentManagerProvider
  options={{
    mode: 'offline',
    offlinePolicy: {
      i18n: {
        defaultProfile: 'default',
        messages: {
          default: {
            translations: {
              en: { cookieBanner: { title: 'Privacy choices' } },
            },
          },
          qc: {
            fallbackLanguage: 'fr',
            translations: {
              en: { cookieBanner: { title: 'Quebec Privacy Settings' } },
              fr: { cookieBanner: { title: 'Paramètres de confidentialité du Québec' } },
            },
          },
        },
      },
      policyPacks: [
        {
          id: 'qc_opt_in',
          match: { regions: [{ country: 'CA', region: 'QC' }] },
          i18n: { messageProfile: 'qc' },
          consent: { model: 'opt-in', expiryDays: 365 },
          ui: { mode: 'banner' },
        },
        {
          id: 'default_world',
          match: { isDefault: true },
          consent: { model: 'none' },
          ui: { mode: 'none' },
        },
      ],
    },
    overrides: {
      country: 'CA',
      region: 'QC',
    },
  }}
>

Notes:

  • offlinePolicy is only used in offline mode.
  • Treat offline policies as a development/testing tool or resilience fallback, not the primary production source of truth.
  • offlinePolicy.i18n lets offline mode mirror hosted messageProfile and profile-local fallbackLanguage behavior.
  • Omitting offlinePolicy.policyPacks uses the built-in synthetic opt-in fallback banner. Hosted network fallback uses the same opt-in banner.
  • offlinePolicy: { policyPacks: [] } is explicit no-banner mode.
  • In hosted mode, backend policyPacks remain the source of truth — frontend offline policies never override a live backend decision.

Read the full guide at Policy Packs and the conceptual model at Policy Packs Concept.

Props

Warning: ExtractedTypeTable: Could not extract "ConsentManagerProviderProps" from "./packages/react/src/types/consent-manager.ts" using base path "/vercel/path0/apps/c15t-docs/.leadtype/c15t". Verify the path/name and that the file is included by your tsconfig.

Loading…