React Cookie Consent: A Banner That Actually Blocks Scripts

Keep the visitor's choice in React state, save it to a first-party cookie or browser storage, and render analytics or ad scripts only when the matching category is granted. A small hook plus one banner component covers it. If you prefer a package, react-cookie-consent gives a ready banner with props for decline buttons, cookie name and expiry.

Two ways to add consent to a React site
Build it yourselfreact-cookie-consent package
Categories (analytics, ads)Yes, any you needOne accept or decline value by default
Reject buttonYesOff by default; enableDeclineButton turns it on
Where the choice is storedYour choice of cookie or storageA cookie named CookieConsent by default
Default expiryYou set it365 days
LicenceYour own codeMIT

Updated

The hard part of a consent banner is not the box on screen, it is making sure nothing non-essential runs before the visitor says yes. In React that is a state problem: one source of truth for the choice, and components that read it before injecting a script tag. The live demo below does exactly that, so you can watch the analytics loader stay off until you accept.

Live demo: a consent banner that gates scripts

Pretend website

We use cookies for analytics and ads only if you agree.

Reject all Accept all Choose

What would load

No choice yet, so nothing optional loads.

Consent Mode update sent

None yet.

Cookie settings

The demo remembers your choice in this browser only. Nothing is sent anywhere.

Build it yourself: hook, banner and gated script

Three small pieces. They use no library, run in any React 18 or 19 app, and match the demo above.

useConsent hook

Reads the saved choice once on the client, and saves plus broadcasts every change.

import { useEffect, useState } from "react";

const KEY = "consent-v1";

export function useConsent() {
  const [consent, setConsent] = useState(null); // null = not chosen yet

  useEffect(() => {
    try {
      const saved = localStorage.getItem(KEY);
      if (saved) setConsent(JSON.parse(saved));
    } catch {}
  }, []);

  function save(next) {
    setConsent(next);
    try { localStorage.setItem(KEY, JSON.stringify(next)); } catch {}
    if (typeof window.gtag === "function") {
      window.gtag("consent", "update", {
        analytics_storage: next.analytics ? "granted" : "denied",
        ad_storage: next.ads ? "granted" : "denied",
        ad_user_data: next.ads ? "granted" : "denied",
        ad_personalization: next.ads ? "granted" : "denied",
      });
    }
  }

  function reset() {
    setConsent(null);
    try { localStorage.removeItem(KEY); } catch {}
  }

  return { consent, save, reset };
}

ConsentBanner component

Accept and Reject sit side by side; category boxes start unticked.

import { useState } from "react";

export function ConsentBanner({ consent, save }) {
  const [open, setOpen] = useState(false);
  const [pick, setPick] = useState({ analytics: false, ads: false });
  if (consent) return null;

  return (
    <div role="dialog" aria-label="Cookie consent" className="cookie-banner">
      <p>We use cookies for analytics and ads only if you agree.</p>
      {open && (
        <fieldset>
          <label><input type="checkbox" checked disabled /> Necessary</label>
          <label><input type="checkbox" checked={pick.analytics}
            onChange={(e) => setPick({ ...pick, analytics: e.target.checked })} /> Analytics</label>
          <label><input type="checkbox" checked={pick.ads}
            onChange={(e) => setPick({ ...pick, ads: e.target.checked })} /> Advertising</label>
          <button onClick={() => save(pick)}>Save choices</button>
        </fieldset>
      )}
      <button onClick={() => save({ analytics: false, ads: false })}>Reject all</button>
      <button onClick={() => save({ analytics: true, ads: true })}>Accept all</button>
      <button onClick={() => setOpen(true)}>Choose</button>
    </div>
  );
}

Load a script only after consent

The script tag is created only when the category is granted, so a refusal means it never exists.

import { useEffect } from "react";

export function AnalyticsLoader({ allowed, src }) {
  useEffect(() => {
    if (!allowed) return;
    const s = document.createElement("script");
    s.src = src;
    s.async = true;
    document.head.appendChild(s);
  }, [allowed, src]);
  return null;
}

// In your root layout:
// const { consent, save } = useConsent();
// <AnalyticsLoader allowed={!!consent?.analytics} src="https://example.com/analytics.js" />
// <ConsentBanner consent={consent} save={save} />

Using the react-cookie-consent package

Install it with npm install react-cookie-consent. These are the props and helpers its README documents, with their defaults, as of October 2026.

react-cookie-consent props and defaults
Prop or helperDefaultWhat it does
location"bottom"Where the bar sits: top, bottom or none
buttonText"I understand"Accept button text
enableDeclineButtonfalseShows a decline button
declineButtonText"I decline"Decline button text
cookieName"CookieConsent"Name of the cookie that stores the answer
expires365Days until the cookie expires
onAccept / onDecline() => {}Run your code when the visitor answers
getCookieConsentValue(name)Reads the stored answer
resetCookieConsentValue()Removes the stored answer so the bar shows again

Banner with a decline button

Accept loads analytics; Decline does nothing optional. The check on first load covers returning visitors.

import CookieConsent, { getCookieConsentValue } from "react-cookie-consent";
import { useEffect } from "react";

function loadAnalytics() {
  const s = document.createElement("script");
  s.src = "https://example.com/analytics.js";
  s.async = true;
  document.head.appendChild(s);
}

export default function App() {
  useEffect(() => {
    if (getCookieConsentValue("site-consent") === "true") loadAnalytics();
  }, []);

  return (
    <CookieConsent
      cookieName="site-consent"
      buttonText="Accept"
      enableDeclineButton
      declineButtonText="Reject"
      onAccept={loadAnalytics}
    >
      We use analytics cookies only if you agree.
    </CookieConsent>
  );
}

Why gating beats hiding

A banner that only hides itself after Reject, while the analytics snippet in index.html has already run, records the visit anyway. Moving the snippet into a component that renders only after consent is the fix.

For tags you cannot move, such as ones a platform injects for you, use that platform's consent setting or Consent Mode so the tag runs in its restricted state until the update arrives.

Testing your banner

Open the site in a private window, open your browser's developer tools and check the Network panel: before you press anything, no analytics or ad requests should appear. Then accept and confirm they start.

Repeat with Reject all and reload a second page to confirm the refusal is remembered.

How it works

  1. A useConsent hook reads any saved choice when the app starts and exposes it with setter functions.
  2. The banner renders only while no choice is saved, and offers Accept all, Reject all and per-category toggles that start off.
  3. Script loaders check the hook before adding their script tag, so a refusal means the tag is never added.
  4. If you use Google tags, the same setter sends a Consent Mode update with the granted categories.
  5. A Cookie settings link anywhere in the app clears the saved choice so the banner shows again.

Tips

Frequently asked questions

Does react-cookie-consent block scripts for me?

No. Its README describes a banner that stores the visitor's answer in a cookie and calls onAccept or onDecline. Loading or skipping your analytics is still your job, for example by checking getCookieConsentValue before you add the script.

How do I add a decline button to react-cookie-consent?

Set the enableDeclineButton prop; it is false by default. The button text defaults to I decline and can be changed with declineButtonText, and onDecline runs when it is pressed.

How do I let visitors change their mind?

With your own hook, clear the saved value and the banner returns. With the package, call resetCookieConsentValue, passing the same cookie name you gave the banner if you changed it from the default.

Should I store consent in a cookie or in browser storage?

Either works for the browser. A cookie can also be read by your server, which helps if the server decides which tags to print. Browser storage keeps the value out of every request. Both are allowed for remembering the choice itself.

Where do Consent Mode calls go in React?

Put the gtag consent default in the HTML head before any Google tag, outside React, so it runs first. Call gtag('consent', 'update', ...) from your Accept handler inside React.

Related: Cookie consent banner examples, Cookie policy generator, Privacy policy generator, GDPR statement generator.

Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.

More in Guides

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap