ConsentStackDocs

React SDK

Integrate ConsentStack into React and Next.js apps with hooks and components.

React bindings for ConsentStack. Use hooks to read consent state, conditionally render components, and build custom consent UIs — all with full TypeScript support.

Installation

npm install @consentstack/react

Peer dependencies: react and react-dom ^18 or ^19.

Quick start

Choose the approach that fits your needs:

ApproachWhen to use
ConsentStackYou just need the banner. No hooks, no conditional rendering.
ConsentStackProviderYou need hooks to read consent state, conditionally load scripts, or build a custom UI.

Simple integration

Install the package

pnpm add @consentstack/react

Add to your layout

app/layout.tsx
import { ConsentStack } from '@consentstack/react'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <ConsentStack siteKey="pk_abc123" />
      </body>
    </html>
  )
}

That's it — the consent banner renders automatically.

With hooks

Wrap your app with the provider

app/layout.tsx
import { ConsentStackProvider } from '@consentstack/react'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ConsentStackProvider siteKey="pk_abc123">
          {children}
        </ConsentStackProvider>
      </body>
    </html>
  )
}
import { useConsent } from '@consentstack/react'

function CookieStatus() {
  const { hasConsent, showPreferences, error } = useConsent()

  if (error) return <p>Failed to load consent: {error.message}</p>

  return (
    <div>
      <p>Analytics: {hasConsent('analytics') ? 'Allowed' : 'Denied'}</p>
      <button onClick={() => showPreferences()}>
        Manage preferences
      </button>
    </div>
  )
}

Component props

ConsentStack

PropTypeDefaultDescription
siteKeystringrequiredYour site's public key from the ConsentStack dashboard.
debugbooleanfalseLog SDK initialization and consent events to the console.
cdnUrlstring"https://cdn.consentstack.io/consent.js"Override the SDK script URL (useful for self-hosting).
timeoutnumber15000Timeout in milliseconds for SDK script loading.

ConsentStackProvider

Accepts all ConsentStack props plus:

PropTypeDefaultDescription
mode"banner" | "headless""banner""banner" shows the consent UI automatically. "headless" hides it so you can build your own.
childrenReactNoderequiredYour app tree.

Hooks

useConsent

Returns the full consent state and control methods. Must be used within a ConsentStackProvider.

interface UseConsentReturn {
  /** Current consent state, or null if not yet given */
  consent: Record<string, boolean> | null

  /** The loaded consent config, or null if not loaded */
  config: ConsentConfig | null

  /** True while the SDK is initializing */
  isLoading: boolean

  /** True once the SDK is ready and consent state is known */
  isReady: boolean

  /** Error if SDK failed to load, null otherwise */
  error: Error | null

  /** Update consent programmatically — logs to the server */
  setConsent: (categories: Record<string, boolean>) => Promise<void>

  /** Check if a specific category has consent */
  hasConsent: (category: string) => boolean

  /** Programmatically show the consent banner */
  showBanner: () => void

  /** Open the preferences panel */
  showPreferences: () => void

  /** Close the preferences panel */
  hidePreferences: () => void

  /** Whether the preferences panel is currently open */
  isPreferencesOpen: boolean
}

The config object uses the ConsentConfig type from the JS SDK — see the JavaScript API reference for the full shape.

useConsentValue

An optimized hook for checking a single consent category. Uses useSyncExternalStore under the hood, so your component only re-renders when that specific category value changes.

function useConsentValue(category: string): boolean

Returns true if the category has consent, false otherwise. Returns false during SSR and while the SDK is loading — a safe default that prevents scripts from firing before consent is confirmed.

import { useConsentValue } from '@consentstack/react'

function AnalyticsLoader() {
  const hasAnalytics = useConsentValue('analytics')
  if (!hasAnalytics) return null
  return <script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX" async />
}

useConsentEvent

Subscribe to SDK lifecycle events. Automatically subscribes on mount and unsubscribes on unmount. You don't need to memoize the callback — the hook handles that internally via a ref.

function useConsentEvent<T extends ConsentEventType>(
  event: T,
  callback: (data: ConsentEventData[T]) => void
): void
EventPayloadFires when
"ready"{ config, consent }SDK has initialized and config is loaded.
"consent"Record<string, boolean>Consent state changes.
"error"ErrorAn SDK error occurs.
"preferences:open"undefinedPreferences panel opens.
"preferences:close"undefinedPreferences panel closes.
import { useConsentEvent } from '@consentstack/react'

function PreferencesTracker() {
  useConsentEvent('preferences:open', () => {
    analytics.track('consent_preferences_opened')
  })

  useConsentEvent('error', (error) => {
    console.error('SDK error:', error)
  })

  return null
}

Error handling

The provider exposes errors from SDK initialization. If the CDN is unreachable or the script fails to load, error will contain the reason.

function ConsentFallback() {
  const { error, isReady } = useConsent()

  if (error) {
    return <p>Consent system unavailable. Some features may be limited.</p>
  }

  if (!isReady) return null

  return <YourApp />
}

The SDK times out after 15 seconds by default. If the CDN is slow or blocked, isLoading will become false and error will contain a descriptive timeout message. Customize the timeout with the timeout prop on ConsentStackProvider or ConsentStack.

Common patterns

Conditional script loading

Block third-party scripts until the visitor grants consent:

import { useConsentValue } from '@consentstack/react'
import Script from 'next/script'

function ThirdPartyScripts() {
  const analyticsAllowed = useConsentValue('analytics')
  const marketingAllowed = useConsentValue('marketing')

  return (
    <>
      {analyticsAllowed && (
        <Script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX" strategy="afterInteractive" />
      )}
      {marketingAllowed && (
        <Script src="https://connect.facebook.net/en_US/fbevents.js" strategy="afterInteractive" />
      )}
    </>
  )
}

Custom preferences UI (headless mode)

Set mode="headless" on the provider to suppress the default banner entirely. Then build your own UI using the hook methods:

app/layout.tsx
<ConsentStackProvider siteKey="pk_abc123" mode="headless">
  {children}
</ConsentStackProvider>
components/cookie-preferences.tsx
import { useConsent } from '@consentstack/react'

function CookiePreferences() {
  const { consent, config, setConsent, isReady, showPreferences, hidePreferences } = useConsent()

  if (!isReady || !config) return null

  const handleToggle = (categoryId: string, enabled: boolean) => {
    setConsent({ ...consent, [categoryId]: enabled })
  }

  return (
    <div>
      <h2>Cookie Preferences</h2>
      {config.categories.map((cat) => (
        <label key={cat.id}>
          <input
            type="checkbox"
            checked={consent?.[cat.id] ?? cat.default}
            disabled={cat.required}
            onChange={(e) => handleToggle(cat.id, e.target.checked)}
          />
          <span>{cat.name}</span>
          <p>{cat.description}</p>
        </label>
      ))}
    </div>
  )
}

In headless mode, you can call showBanner() to display the default ConsentStack banner on demand — useful as a fallback while you build your custom UI.

TypeScript

All exports are fully typed. The key types you can import:

import type {
  ConsentConfig,
  ConsentCategory,
  ConsentStackAPI,
  ConsentEventType,
  ConsentEventData,
  UseConsentReturn,
  ConsentStackProps,
  ConsentStackProviderProps,
} from '@consentstack/react'

ConsentConfig, ConsentCategory, ConsentStackAPI, ConsentEventType, and ConsentEventData are re-exported from the JS SDK — the React package is always in sync.

What's next