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.
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.
'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.
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
}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())