Consenti

Framework Integrations

Consenti is framework-agnostic at its core — it's a plain TypeScript class that touches the DOM. What changes per framework is when you initialise it (after mount, not during server render) and how you clean up on unmount. This guide gives you a complete, copy-paste snippet for each major framework.

ℹ️All snippets assume npm install @consenti/ui is already done. No CSS import is needed — ConsentiSetup injects its own <style> tag at runtime unless you set core.disableCssTemplate: true.

React

Use useEffect to initialise after mount and return widget.destroy()as the cleanup function. This handles hot reloads and strict mode double-invocation correctly.

components/ConsentSetup.tsx
tsx
'use client' // remove this line outside Next.js

import { useEffect } from 'react'
import { ConsentiSetup } from '@consenti/ui'

export function ConsentSetup() {
  useEffect(() => {
    const widget = new ConsentiSetup({
      compliance: { type: 'opt-in' },
      core: { locale: 'auto' },
    })
    return () => widget.destroy()
  }, [])

  return null
}

The useConsent hook

For reading consent state in components, use the bundled React hook from the @consenti/ui/react subpath. It's SSR-safe — returns false during server render.

components/AnalyticsGate.tsx
tsx
import { useConsent } from '@consenti/ui/react'

export function AnalyticsGate() {
  const { hasConsent, consent, showModal } = useConsent()

  if (!hasConsent) {
    return (
      <button onClick={showModal}>
        Enable Analytics
      </button>
    )
  }

  if (consent?.analytics === 'granted') {
    return <span>Analytics are on</span>
  }

  return <span>Analytics are off</span>
}

Next.js App Router

In Next.js App Router, the widget must run in a Client Component because it accesses the DOM. Create a wrapper component with 'use client' and add it to your root layout.

'use client'

import { useEffect, useRef } from 'react'
import type { ConsentiSetup as WidgetType } from '@consenti/ui'

export function ConsentSetup() {
  const widgetRef = useRef<WidgetType | null>(null)

  useEffect(() => {
    let widget: WidgetType
    import('@consenti/ui').then(({ ConsentiSetup }) => {
      widget = new ConsentiSetup({
        api: {
          enabled: true,
          baseUrl: process.env.NEXT_PUBLIC_API_URL,
        },
        core: { autoHonorGPC: true },
      })
      widgetRef.current = widget
    })
    return () => widgetRef.current?.destroy()
  }, [])

  return null
}
💡The dynamic import('@consenti/ui') ensures the browser-only widget code is never evaluated during SSR, even if Next.js renders the Client Component on the server (which it does for the initial HTML). The useEffect callback only runs client-side.

Vue 3 / Nuxt

Use onMounted and onBeforeUnmount. Dynamic import inside onMounted ensures the widget is never evaluated during Nuxt SSR.

<script setup lang="ts">
import { onMounted, onBeforeUnmount } from 'vue'

let widget: { destroy?: () => void } | null = null

onMounted(async () => {
  const { ConsentiSetup } = await import('@consenti/ui')
  widget = new ConsentiSetup({ compliance: { type: 'opt-in' } })
})

onBeforeUnmount(() => widget?.destroy?.())
</script>

<template><slot /></template>

Angular

Use the built-in Angular service from @consenti/ui/angular, or create your own service using isPlatformBrowser to guard the SSR boundary.

import { ConsentiService } from '@consenti/ui/angular'
import { Component, inject } from '@angular/core'

@Component({ /* ... */ })
export class MyComponent {
  consent = inject(ConsentiService)

  openPreferences() {
    this.consent.showModal()
  }
}

Vanilla JS

No framework? No problem. Import the package directly if you have a bundler, or use the CDN/UMD build for zero-tooling setups.

import { ConsentiSetup } from '@consenti/ui'

const widget = new ConsentiSetup({ compliance: { type: 'opt-in' } })

document.querySelector('#cookie-settings')
  ?.addEventListener('click', () => widget.showModal())

Frequently asked questions