Skip to main content

React

Components

Component reference

c15t/react exports the provider, the consent UI and the hooks from one import path. Every component except ConsentProvider and ConsentTheme reads the runtime the provider creates, so render them inside ConsentProvider.

ComponentRendersReference
ConsentProviderThe consent runtime, which loads the policy, stores the choice and loads scripts. No markup of its ownConsentProvider
ConsentBannerThe banner, when the visitor's policy asks for oneConsentBanner
ConsentDialogThe preference center as a modalConsentDialog
ConsentWidgetThe preference center inline in a page, for a privacy routeConsentWidget
ConsentDialogLinkAn unstyled button that opens the dialog, for your footerConsentDialogLink
ConsentDialogTriggerA floating, draggable button that opens the dialog. ConsentDialogTriggerToolbar adds your own actions beside itConsentDialogTrigger
ConsentGateIts children while a category is allowed, a placeholder otherwiseConsentGate
ConsentThemeA <style> element with your theme tokensCustomize
DevTools from c15t/react/devtoolsA development panel for consent state, scripts and eventsDevTools

Mount one ConsentProvider at the root of the app and keep it mounted across client navigation. The provider reads mode and its module options once, when it mounts.

Start with the provider, banner and dialog

Most apps start with four components. ConsentProvider holds the runtime, ConsentBanner and ConsentDialog ask for a choice, and a ConsentDialogLink lets visitors reopen their preferences. The quickstart puts them in one file:

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>
);

Add ConsentWidget, ConsentDialogTrigger or ConsentGate later, where a page needs them. Keep ConsentDialog mounted even when you add a widget, because the banner's Customize button and every dialog link open it.

Server-rendered React apps

React Router framework mode, Remix and other server-rendered React apps use the same components. ConsentProvider renders on the server without touching browser APIs, and the banner mounts after hydration, once the browser has resolved the policy. Rendering explains the trade-off.

IAB TCF components

Import IABProvider, IABConsentBanner and IABConsentDialog from c15t/react/iab, render them inside ConsentProvider next to ConsentBanner and ConsentDialog, and load c15t/react/styles.css and c15t/react/iab/styles.css with styles: false. The stock banner and dialog stay closed under an IAB policy. See IAB TCF.

Other entry points

ImportExportsUse
c15t/react/consent-banner, c15t/react/consent-dialog, c15t/react/consent-widget, c15t/react/consent-dialog-link, c15t/react/consent-dialog-trigger, c15t/react/consent-gateOne component eachA module that needs one component without the rest of the adapter
c15t/react/headlessHeadless hooksYour own banner and dialog markup. See headless
c15t/react/serverfetchSSRData and its helpersResolve /init in a server loader. See rendering
c15t/react/devtoolsDevToolsDevelopment only

Check the components

Load the app in a private window under a policy that asks for a choice:

  1. The banner shows, and DevTools Network has no requests to your vendors' hosts.
  2. Click the footer's ConsentDialogLink. The dialog opens with every optional category off.
  3. Allow one category and save. Its vendor requests appear, and a ConsentGate for that category mounts its children.
  4. Reload. The banner stays closed, and the link still reopens the dialog.