Skip to main content

Next.js Components

DevTools

DevTools is a floating panel for the c15t v3 kernel. Use it during development to inspect consent values, scripts, location, the resolved policy, IAB state, events, and consent actions. The framework adapter reads the kernel from the nearest v3 provider. It never discovers a store through a window global.

Installation

No extra package is required. The React adapter and its engine are included with the c15t package. Install @c15t/dev-tools directly only when you need the imperative createDevTools({ kernel }) API.

Add DevTools to ConsentRoot in development

Keep the ConsentRoot from your router setup. Add this client component as one of its children, alongside the banner and dialog. It uses the existing runtime, including its prefetch result and configured transport.

components/consent-dev-tools.tsx
'use client';

import dynamic from 'next/dynamic';

const DevTools =
	process.env.NODE_ENV === 'development'
		? dynamic(
				() => import('c15t/next/devtools').then(({ DevTools }) => DevTools),
				{ ssr: false }
			)
		: () => null;

export function ConsentDevTools() {
	return <DevTools />;
}

Import ConsentDevTools into your root layout or pages/_app.tsx and render <ConsentDevTools /> inside ConsentRoot. Keep its state and other props unchanged. A setup that uses ConsentProvider directly can place the same component inside that provider.

The umbrella import is c15t/next/devtools. When installing the dedicated Next.js package, use @c15t/nextjs/devtools.

Configuration

<DevTools
  position="bottom-right"
  defaultOpen={false}
  defaultTab="consents"
  maxEvents={100}
  disabled={false}
/>

position accepts any corner: top-left, top-right, bottom-left, or bottom-right. Set defaultOpen to open the initial panel and defaultTab to choose that panel. maxEvents limits captured kernel and script events. Set disabled to prevent mounting without removing the component. The panel renders inside a shadow root by default, so page CSS does not reach it. shadow={false} mounts it in the light DOM with its stylesheet in <head>.

While ConsentDialogTriggerToolbar or the ready-made ConsentDialogTrigger is visible, the trigger shows a DevTools button and DevTools hides its own floating launcher, so one control occupies the corner. The button sits at the end of the toolbar farthest from its corner. The panel opens just past the toolbar, aligned with its outer edge, and follows it when a visitor drags the toolbar to another corner. position applies again once no trigger is visible.

ConsentDialogTrigger becomes a two-button toolbar while DevTools is mounted. A trigger composed from ConsentDialogTrigger.Root keeps its own markup, and DevTools keeps its floating launcher beside it.

DevTools brings its launcher back whenever no trigger is visible, for example while showWhen="after-prompt" waits for a choice. A production build that loads DevTools only in development shows no DevTools button in the trigger.

With the imperative engine, call devTools.dock({ position, inline, block }) to hide the launcher and anchor the panel inline and block pixels from the viewport edges of position's corner. Call devTools.dock(null) to restore the launcher.

Panels

Accept and reject apply only to categories displayed by the provider, leaving hidden consent values unchanged. necessary always remains enabled. The React adapter uses the current policy categories by default.

For an imperative integration with a narrower UI, pass getConsentCategories: () => ['necessary', 'measurement'] to createDevTools. The getter is read again when an action runs. Headless UI actions can use kernel.commands.save('all', { categories: displayed }) or kernel.commands.save('none', { categories: displayed }). These scoped bulk saves use a custom transport action when they cover only part of the policy. Actions covering the whole policy retain all or necessary. Without an explicit scope or policy categories, bulk saves cover all known categories.

PanelWhat it shows
ConsentsInspect and save categories, accept all, or reject optional categories
ScriptsConfigured scripts, loading status, search, and external page resources
LocationResolved country and region, and location overrides
PolicyResolved policy and UI configuration
IABEdit vendors, purposes, legitimate interests, and special features; save and copy TC strings
EventsTimeline of consent changes, kernel events, and script lifecycle events
ActionsShow the banner, open preferences, hide consent UI, or refresh consent data

Script inspection

Set defaultTab="scripts" to open script inspection first. It reads all script loaders attached to the current provider's kernel, including loaders created before DevTools mounts. Search by script ID, category, URL, or status, then expand a script to inspect its configuration and latest lifecycle event.

loading means an external script was inserted; loaded means its browser load event fired, or an inline script or callback-only integration mounted. error reports a loader error. blocked means consent requirements were not met, and pending means an eligible script has not mounted. present means the loader reused an element without a confirmed load result. retained means consent was revoked but persistAfterConsentRevoked kept the element. Retained scripts still receive onConsentChange with hasConsent: false, so their integrations can send the vendor's consent-revocation command. alwaysLoad bypasses the loading gate, not consent: callbacks receive the actual consent state and updates across categories for integrations such as Google Consent Mode.

Script details distinguish allowedToLoad from consentGranted. An alwaysLoad integration can be allowed to load while its consent is denied. Diagnostics expose these as eligible and hasConsent, respectively.

Lifecycle diagnostics work even when legacy debug forwarding is disabled. The page scan lists external scripts and iframes present in the DOM; it does not prove that they loaded successfully or were consent-gated.

For custom inspection tools, import getScriptDiagnostics(kernel) and subscribeScriptDiagnostics(kernel, listener) from c15t/modules/script-loader. The subscription reports loader registration, updates, disposal, and lifecycle events. Read a fresh snapshot after changes and call the returned unsubscribe function during cleanup.

IAB editing

Under an IAB policy, the Consents tab is read-only. Edit and save vendors and purposes in the IAB tab so the derived categories and TC string stay consistent.

The IAB panel connects to the existing @c15t/iab module attached to the provider's kernel. It does not create a second CMP or replace __tcfapi. Controls become available after initialization, when the current policy uses IAB and the vendor list is loaded.

Choose Vendors, Purposes, or Special features, then search by name or ID. Vendors include the provider's custom vendors. Long lists show 20 entries per page. Legitimate-interest controls appear for declared legitimate interests. Search only filters the view; Accept all IAB and Reject all IAB apply to the configured choices, including custom vendors.

Toggles update live IAB state and script gating immediately. Use Save IAB consent to generate a fresh TC string and run the configured save transport. The panel displays the last generated string; unsaved edits are not represented in it. Copy TC string confirms success or reports clipboard failures. Raw IAB data remains available in an expandable section.

IAB saves normally write the TC string to its standard cookie and localStorage. For an in-memory playground, set persistence: false in the IAB module options as well as disabling the provider's core persistence. The IAB option prevents TC-string storage writes; it does not disable the configured save transport or remove previously stored values.

Save and refresh actions show pending, success, and failure feedback. Controls are disabled while a request is pending. A failed save does not roll back the live choices; retry to record them.

For custom inspection tools, getIABControls(kernel) and subscribeIABControls(kernel, listener) are exported from c15t. The getter returns undefined before module initialization and after disposal. A snapshot without an attached IAB module is read-only in DevTools.

TanStack Devtools

Each embedded panel owns its event history and subscriptions. Unmounting it destroys the instance; remounting starts a new history. If you need to capture events while switching plugins, keep the c15t panel mounted. This differs from the old globally shared DevTools store.

The React v3 DevTools adapter exports a panel component and plugin factory that match TanStack Devtools' plugin API:

import * as React from 'react';
import { useRouter } from '@tanstack/react-router';
import { TanStackDevtools } from '@tanstack/react-devtools';
import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools';
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
import { c15tDevtools } from 'c15t/react/devtools';

export function AppDevtools() {
  const router = useRouter();

  return (
    <TanStackDevtools
      plugins={[
        {
          name: 'TanStack Query',
          render: <ReactQueryDevtoolsPanel />,
        },
        {
          name: 'TanStack Router',
          render: <TanStackRouterDevtoolsPanel router={router} />,
        },
        c15tDevtools(),
      ]}
    />
  );
}

In a Next.js app, import c15tDevtools from c15t/next/devtools instead. Scoped installs use @c15t/react/devtools and @c15t/nextjs/devtools.

The c15t panel follows the TanStack Devtools light or dark theme, not your app's consent theme. If you write the plugin entry yourself, use a render function and pass the theme to C15tTanStackDevtoolsPanel:

import { C15tTanStackDevtoolsPanel } from 'c15t/react/devtools';

const c15tPlugin = {
  name: 'Consent',
  render: (_element: HTMLElement, { theme }: { theme: 'light' | 'dark' }) => (
    <C15tTanStackDevtoolsPanel theme={theme} />
  ),
};

Props

Warning: ExtractedTypeTable: Could not extract "ConsentDevToolsProps" from "./packages/react/src/devtools.tsx" using base path "/vercel/path0/apps/c15t-docs/.leadtype/c15t". Verify the path/name and that the file is included by your tsconfig.

Loading…