Skip to main content

React Scripts and embeds

Scripts

Register vendor scripts

Pass vendor loaders to ConsentProvider through options.scripts. The provider loads each script when its category is allowed and removes it when the category is denied. The quickstart registers PostHog this way in src/consent.tsx:

src/consent.tsx
import { posthog } from '@c15t/integrations/posthog';
import {
	ConsentBanner,
	ConsentDialog,
	ConsentDialogLink,
	ConsentProvider,
	hosted,
} from 'c15t/react';
import type { ReactNode } from 'react';

const options = {
	// Asks VITE_C15T_BACKEND_URL for each visitor's policy.
	mode: hosted(),
	scripts: [
		posthog({
			id: 'phc_your_project_key',
			initOptions: { cookieless_mode: 'never' },
			loadMode: 'after-consent',
		}),
	],
};

export const Consent = ({ children }: { children: ReactNode }) => (
	<ConsentProvider options={options}>
		{children}
		<ConsentBanner />
		<ConsentDialog />
		<footer>
			<ConsentDialogLink>Privacy settings</ConsentDialogLink>
		</footer>
	</ConsentProvider>
);

Each helper from @c15t/integrations sets its own category and a stable id. Remove every other loader for the same vendor, such as a <script> tag in index.html or an SDK you initialize at module level, so the vendor loads once and only through c15t. Integrations lists every helper, and building integrations covers a vendor without one.

  • Allowed. The script loads, or its SDK is started, the first time its category is allowed.
  • Denied later. The provider removes the script element. Code that already ran keeps running, so after the visitor turns off a category they had allowed, the provider reloads the page once the save finishes. Set reloadOnConsentRevoked: false only if every gated vendor stops itself.
  • alwaysLoad helpers. Some integrations, such as the Google Consent Mode helpers by default, load before consent and pass the visitor's choice to the vendor. Read the vendor's guide; a category on a script does not always mean zero requests.

When the script loader downloads

The script loader is a separate chunk, downloaded only when scripts is not empty. ConsentProvider loads it when it mounts. The Next.js and TanStack Start ConsentRoot start the download earlier, during their first render, when the visitor's consent already lets a script run.

Embeds and other requests

Scripts cover vendor code c15t loads for you. For the rest:

  • Embeds keeps iframes out of the page with ConsentGate or the iframe blocker.
  • Network blocker holds fetch and XHR calls that match a rule until their category is allowed.

Clear stored data after revocation

Removing a script does not delete the cookies or storage entries it wrote. Add clearOnRevocation to the provider options to delete the entries you list for each category when it is denied. The option is read once, when the provider mounts. Clear on revocation covers configuration and browser limits.

Let visitors turn off one vendor

Optional: a visitor can allow marketing and still switch off one vendor in it. Skip this unless your site needs per-vendor choices. Pass vendors to the provider options. Helpers from @c15t/integrations already carry their vendor slug. See vendor consent.

To send your own events only to allowed integrations, see send events only to allowed integrations.

Send your own analytics events

useEventDispatcher() returns the provider's event dispatcher. It sends to the scripts you registered and checks consent when an event goes out. Call pageview from your router with the current path:

src/analytics-events.tsx
import { useEventDispatcher } from 'c15t/react';
import { useEffect } from 'react';

/**
 * Sends a PostHog pageview for each new path. Call it once, inside
 * `ConsentProvider`, with your router's current path, such as
 * `useLocation().pathname` from React Router.
 */
export const usePageviews = (path: string) => {
	const events = useEventDispatcher({ pageviews: ['posthog'] });
	useEffect(() => {
		events.pageview(path);
	}, [events, path]);
};

export const SearchButton = ({ resultCount }: { resultCount: number }) => {
	const events = useEventDispatcher();
	return (
		<button
			type="button"
			onClick={() => {
				events.track('search', { resultCount });
			}}
		>
			Search
		</button>
	);
};

track sends to every registered script with an event API and drops the event while measurement is denied. pageview sends only to the integrations named in pageviews, and the first call records the path without sending. Every dispatcher shares one pageview history for the provider's lifetime. See send events only to allowed integrations for the supported vendors.

Check the scripts

Open DevTools Network in a private window, filter by each vendor's domain and reload.

  1. Before a choice under an opt-in policy, no vendor script loads.
  2. Allow one category. Only that category's vendors load.
  3. Turn the category off. The page reloads and the vendor stays absent.
  4. Reload again. The rejection holds.

Verify consent has the full checklist.