Skip to main content

React

Quickstart

Before you start

This guide sets up a client-rendered React app built with Vite. The browser loads the page and asks your consent backend for the visitor's policy, then shows the banner or runs the scripts the visitor allowed. The backend reads the visitor's location from the request, so regional policies need no extra setup.

  • Next.js or TanStack Start? Use that framework's guide from the framework list. Their adapters resolve consent on the server.
  • React Router framework mode, Remix or another server-rendered React app? This guide still works. The server renders the page without consent state and the browser resolves it after hydration. Read rendering for what that means for the first paint.

You need an Inth project. In the project, set the policy rules for your regions, add your site's origin to the trusted origins, and copy the backend URL. Choose your setup covers self-hosting and offline mode.

The guide uses hosted() mode. Policy, translation and vendor edits in your project reach visitors on their next page load, without a rebuild.

Install c15t

npm install c15t@alpha @c15t/integrations@alpha

c15t contains the React adapter at c15t/react and the Vite plugin at c15t/build. @c15t/integrations holds the vendor loaders used below.

Add the Vite plugin

Add consentManifest to vite.config.ts:

vite.config.ts
import react from '@vitejs/plugin-react';
import { consentManifest } from 'c15t/build';
import { defineConfig } from 'vite';

export default defineConfig({ plugins: [react(), consentManifest()] });

The plugin reads VITE_C15T_BACKEND_URL. Set it to your project's backend URL, exactly as Inth shows it, including any path prefix, in .env or wherever you build:

.env
VITE_C15T_BACKEND_URL=https://your-project.inth.app

If your app already sets VITE_INTH_PROJECT_URL for other Inth SDKs, that works too. When both are set, VITE_C15T_BACKEND_URL wins.

consentManifest hands the backend URL to hosted() through c15t/generated. No file is written into your project. Without the plugin, pass the URL as hosted({ backendURL }).

The plugin also adds a small script to index.html that sends /init while your JavaScript downloads, so the banner shows sooner. Rendering covers when to turn it off.

Mount the provider

Create src/consent.tsx. ConsentProvider creates the consent runtime and loads the scripts. ConsentBanner and ConsentDialog render when the policy asks for them, and ConsentDialogLink keeps preferences reachable after the banner closes.

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

hosted() makes one /init request to your backend, started early by the script in index.html. The backend picks the policy from the visitor's location and language and returns its current version. Consent choices go to the same backend. Vite reads VITE_C15T_BACKEND_URL at build time, so set it before you build, not when you serve the files.

PostHog waits for measurement permission, so your policy must include that category. Replace phc_your_project_key with your PostHog project key. PostHog uses its EU host; add region: 'us' for a US project. Remove any other PostHog loader from your app so PostHog loads once.

loadMode: 'after-consent' keeps the PostHog SDK off the page until the visitor allows measurement. cookieless_mode: 'never' turns off PostHog's cookieless capture. See integrations for other vendors.

Wrap your app with it in src/main.tsx:

src/main.tsx
import { createRoot } from 'react-dom/client';

import { App } from './app';
import { Consent } from './consent';

const root = document.getElementById('root');
if (!root) {
	throw new Error('Missing #root element');
}
createRoot(root).render(
	<Consent>
		<App />
	</Consent>
);

Keep Consent mounted for the life of the app. If you use a client-side router, render the router inside Consent so navigation does not remount the provider. There is no stylesheet to import: the banner and dialog render their own rules as <style> elements.

Gate your own features

Read a category's permission with useConsent inside the provider:

src/marketing-banner.tsx
import { useConsent } from 'c15t/react';

export function MarketingBanner() {
	return useConsent('marketing') ? <aside>Spring sale</aside> : null;
}

useConsent returns the permission right now. Under an opt-out policy it can be true before the visitor has chosen anything. Read how consent works before you record or report choices. For iframes, use ConsentGate.

Check that it works

Build and preview the production bundle with vite build and vite preview, then open the site in a private window with DevTools open on the Network tab. Test under a policy that asks for a choice, such as an EU opt-in policy.

  1. Before a choice. One /init request goes to your backend URL, starting before your JavaScript finishes downloading, and the banner shows. There are no requests to posthog.com.
  2. Reject All. The banner closes and the vendor requests stay absent.
  3. Reload. The banner stays closed and the vendor requests stay absent.
  4. Privacy settings. The footer link opens the dialog with Analytics (the measurement category) and Marketing off. Turn on Analytics and save. PostHog's array.js loads.
  5. Turn Analytics off again. The page reloads and PostHog does not load.

If the banner never appears, check the /init request to your backend URL. Troubleshooting covers the common causes. Verify consent has the full release checklist.

Next steps

  • Rendering for server-rendered React apps and what the first paint contains.
  • Scripts and embeds for more vendors, network blocking and data cleanup.
  • Customize for colors, fonts, layout and copy.
  • Components for every provider option and component prop.
  • The runnable app in examples/react of the c15t repository contains this setup.