[Consent banner](/consent-banner)

# Add a cookie consent banner to React

c15t puts the banner in your React app as code. It holds tags until the visitor chooses and keeps Google Consent Mode v2 in sync.

[Start for free](/dashboard/sign-up) · [See the code](#install)

Ask your coding agent: `Add inth to my app: inth.com/SKILL.md`

- **Framework**: React apps built with Vite
- **Library**: c15t, open source
- **Code checked**: 28 October 2026

## Install c15t in a React app

c15t is the open source consent library behind Inth Consent. It renders the banner from your own bundle and records each choice in your Inth project.

```bash
npm install c15t @c15t/integrations
```

Add your Inth project URL to `.env`. Copy it from your Inth dashboard. Vite reads it at build time, so set it before you build.

```bash
VITE_C15T_BACKEND_URL=https://your-project.inth.app
```

Add the c15t Vite plugin. It gives the backend URL to `hosted()` and writes no file into your project.

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

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

Code on this page comes from the [c15t React quickstart](https://c15t.com/docs/frameworks/react/quickstart), checked 28 October 2026.

React Router framework mode and Remix also work. The browser resolves consent after hydration there.

## Mount the provider and the banner

Create `src/consent.tsx`. `ConsentProvider` starts consent and loads your scripts. `ConsentBanner` and `ConsentDialog` render when the visitor’s policy asks for them.

`ConsentDialogLink` lets visitors open their preferences again after the banner closes.

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

Wrap your app with it in `src/main.tsx`. Keep `Consent` mounted for the life of the app. If you use a client router, render the router inside `Consent`.

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

There is no stylesheet to import. The banner and the dialog bring their own styles.

## Hold scripts until consent

List each tag in the `scripts` array of `ConsentProvider`. With `loadMode: 'after-consent'`, the PostHog SDK stays off the page until the visitor allows measurement.

Remove every other loader for PostHog, so it loads once and only through c15t. [React scripts and embeds](https://c15t.com/docs/frameworks/react/scripts) covers more vendors, iframes and the network blocker. The [c15t integrations](https://c15t.com/docs/integrations/overview) list the helper for each vendor.

## Gate your own features with useConsent

Read the permission for a category with `useConsent` inside the provider. Show a feature only after the visitor gives consent to its category.

```tsx
import { useConsent } from 'c15t/react';

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

`useConsent` returns the permission at this moment. Under an opt out policy, it can be `true` before the visitor chooses. For iframes, use [`ConsentGate`](https://c15t.com/docs/frameworks/react/components/consent-gate).

## Google Consent Mode v2 in React

The Google tag helper sends the Consent Mode v2 default before your config and an update after each choice. It ships in `@c15t/integrations`.

```ts
import { gtag } from '@c15t/integrations/google-tag';

export const scripts = [gtag({ id: 'G-XXXXXXXXXX', category: 'measurement' })];
```

Add the helper to the `scripts` array in `src/consent.tsx`, next to PostHog.

Measurement maps to `analytics_storage`. Marketing maps to `ad_storage`, `ad_user_data` and `ad_personalization`.

By default the helper loads gtag.js before a choice. Google then gets a default that matches the current permissions. Set `loadMode: 'after-consent'` to send nothing to Google until the visitor accepts.

[Read the developer guide to Consent Mode v2](/blog/developer-guide-to-consent-mode-v2).

## Show the right banner in each region

Your Inth project sets a policy for each region, such as opt in for the EU and UK or opt out for California. With `hosted()`, the browser asks your backend for the policy, and the backend reads the visitor’s location from that request. Regional policies need no extra code.

Test each region before launch. [See how regional policies work](/consent-banner/regions).

## When the banner appears

`hosted()` sends one `/init` request to your backend. The Vite plugin adds a small script to `index.html` that sends this request while your JavaScript downloads, so the banner can show sooner.

For a banner in the first server HTML, use the [Next.js](/consent-banner/nextjs) or [TanStack Start](/consent-banner/tanstack-start) setup. Measure your own pages before and after you add the banner.

## Check that consent gates work

Build with `vite build`, run `vite preview` and open a private window with the Network tab open. Test under an opt in policy, such as the EU policy.

1. Under an opt in policy, no PostHog request leaves the page before a choice.
2. After Reject All and a reload, PostHog stays off.
3. When you turn on measurement in Privacy settings, PostHog loads.
4. When you turn measurement off, the page reloads without PostHog.

Choices post to your Inth project. Check that Inth has a record for each one. The [c15t verify consent guide](https://c15t.com/docs/guides/verify-consent) has the full release checklist.

## Use Inth in production

c15t is open source. Point it at your Inth project, which hosts the backend and keeps the consent records. [See Inth open source](/oss).

c15t also has a local mode with no backend. It keeps no consent records, so the docs advise against it in production.

Want to see what else your repo sends before consent? [Run a free codebase scan](/run-free-codebase-scan).

## Cookie consent banners for other frameworks

The same banner and the same Inth project work in other stacks. See the setup for [Next.js](/consent-banner/nextjs), [TanStack Start](/consent-banner/tanstack-start), [Nuxt](/consent-banner/nuxt), [Vue](/consent-banner/vue), [Astro](/consent-banner/astro), [Svelte](/consent-banner/svelte), [SvelteKit](/consent-banner/sveltekit), [HTML](/consent-banner/html) and [JavaScript](/consent-banner/javascript).

Want to compare React libraries? Read [Inth vs react-cookie-consent](/comparison/react-cookie-consent).

[See how the Inth consent banner works](/consent-banner).

## Questions about React consent

### Does it work with React Router or Remix?

Yes. The server renders the page without consent state, and the browser resolves consent after hydration. For consent in the server HTML, use [Next.js](/consent-banner/nextjs) or [TanStack Start](/consent-banner/tanstack-start).

### How do I load analytics only after consent?

Add the c15t helper for the tag to the `scripts` array with `loadMode: 'after-consent'`. Then remove the tag’s own script.

### How do I set Consent Mode v2 defaults before gtag loads?

Use the c15t Google tag helper. It sends the default before your config and an update after each choice.

### Can I hide my own features until consent?

Yes. `useConsent('marketing')` returns the current permission for that category. Render the feature only when it is `true`.

### Does it show a different banner in each region?

Yes. Set a policy for each region in your Inth project. c15t applies the policy that matches the visitor’s location. [See regional policies](/consent-banner/regions).

### Do I need to import a stylesheet?

No. The banner and the dialog render their own styles.

### How does it compare with react-cookie-consent?

Read [Inth vs react-cookie-consent](/comparison/react-cookie-consent) for a side by side look at the setup.

### Can I run it without a backend?

c15t has a local mode with no backend, but it keeps no consent records. Use your Inth project in production.