React Integration
Reusable React component for the Ekoo widget
The widget auto-loads its config from the backoffice. Appearance attributes are optional overrides — no need to set them if the backoffice is configured.
Core attributes
data-ekooReqstring
Your Ekoo website UUID. Found in the backoffice → Site settings.
data-ekoo-product-idReqstring
Product reference (must match exactly what is in your Ekoo catalog).
data-ekoo-localestring·default:
autoLocale code: fr, en, es, it, de, ar, cn, tw, hk, jp, kr, nl, tr, pl, pt, lu, be, ru. "auto" = navigator.language detection.
data-ekoo-variantstring
Stable reference of a widget configuration. The product, its audios, and its reviewers still come from data-ekoo-product-id; only the widget styling changes.
data-ekoo-review-idstring
ID of a specific audio review. If omitted, the first published review is used.
data-ekoo-on-eventstring (fn name)
Name of a global window function called on each widget event (printed, played-0, played-25…).
Appearance — backoffice overrides
data-ekoo-directionnormal | reverse·default:
normalExpansion direction. normal = left-to-right, reverse = right-to-left.
data-ekoo-scalenumber·default:
1Scale factor (e.g. "1.2" for 20% larger).
data-ekoo-animationstring·default:
pulseAnimation type for the widget icon at rest.
data-ekoo-animation-durationstring·default:
continuousAnimation duration.
data-ekoo-always-openboolean·default:
falseIf "true", the widget stays permanently expanded.
data-ekoo-show-imageboolean·default:
trueShow or hide the product image in the widget.
data-ekoo-not-fully-clickableboolean·default:
falseIf "true", only the play button is clickable.
data-ekoo-autoplayboolean·default:
falseAutomatically start audio playback on load.
data-ekoo-show-transcriptboolean·default:
falseShow a button to read the audio transcript.
data-ekoo-show-speed-buttonboolean·default:
falseShow a playback speed control.
data-ekoo-closed-state-main-textstring
Main CTA text shown when the widget is collapsed.
data-ekoo-closed-state-secondary-textstring
Secondary text below the CTA when the widget is collapsed.
SPA & Shadow DOM
data-ekoo-modespa | static·default:
autoForces rendering mode. Auto-detected (Next.js, Nuxt, React, Vue, Angular, Sapper). Only use if auto-detection fails.
data-shadow-modeopen | closed·default:
open"open" (default) enables inspection, external CSS access and analytics tracking. Set to "closed" to fully isolate the widget.
Global JS config
window.EKOO_FORCE_SPA = trueForces SPA mode globally (alternative to data-ekoo-mode="spa" on each widget).
window.ekooShadowMode = "open"Global Shadow DOM mode (alternative to data-shadow-mode on each widget).
1. TypeScript Declarations
Add the types for the global Ekoo functions to avoid TypeScript errors:
types/ekoo.d.ts
typescript
1declare global {2 interface Window {3 ekooLoad?: () => void4 ekooUnload?: () => void5 ekooReload?: () => void6 }7}89export {}2. EkooWidget Component
components/EkooWidget.tsx
tsx
1'use client' // Next.js App Router only23import { useEffect, useRef } from 'react'45const EKOO_SCRIPT_URL = 'https://app.ekoo.co/widgets/widget-4.0.0-standalone.js'67interface EkooWidgetProps {8 websiteId: string9 productId: string10 locale?: string11}1213export function EkooWidget({14 websiteId,15 productId,16 locale = 'fr',17}: EkooWidgetProps) {18 const containerRef = useRef<HTMLDivElement>(null)1920 useEffect(() => {21 // Load the script if not already present22 let script = document.querySelector<HTMLScriptElement>(23 `script[src="${EKOO_SCRIPT_URL}"]`24 )2526 if (!script) {27 script = document.createElement('script')28 script.src = EKOO_SCRIPT_URL29 script.defer = true30 document.head.appendChild(script)31 }3233 // Initialize the widget once the script is loaded34 const init = () => window.ekooLoad?.()3536 if (script.dataset.loaded === 'true') {37 init()38 } else {39 script.addEventListener('load', () => {40 script!.dataset.loaded = 'true'41 init()42 })43 }4445 // Cleanup on unmount46 return () => {47 window.ekooUnload?.()48 }49 }, [websiteId, productId, locale])5051 return (52 <div53 ref={containerRef}54 data-ekoo={websiteId}55 data-ekoo-product-id={productId}56 data-ekoo-locale={locale}57 />58 )59}3. Usage
app/product/[id]/page.tsx
tsx
1import { EkooWidget } from '@/components/EkooWidget'23export default function ProductPage({ params }: { params: { id: string } }) {4 return (5 <main>6 <h1>My product</h1>7 <EkooWidget8 websiteId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"9 productId={params.id}10 locale="fr"11 />12 </main>13 )14}4. SPA Navigation (React Router)
In a SPA, the component mounts and unmounts on route changes. The useEffect in the component above already handles this case:
- On mount:
ekooLoad()initializes the widget. - On unmount:
ekooUnload()cleans up listeners.
⚠️
Re-initialization on route change
If the productId changes without unmounting the component (e.g. navigating between two products on the same route), call window.ekooReload() to refresh the widget. The component above handles this via the productId dependency in the useEffect.
5. Next.js Notes
- App Router: add
'use client'at the top of the component file (already done in the example). - Pages Router: use
dynamic(() => import(...), { ssr: false })to disable SSR if you encounterwindow is not definederrors. - The Ekoo script manipulates the DOM — it must run on the client side only.
Dynamic import (Pages Router)
tsx
1import dynamic from 'next/dynamic'23const EkooWidget = dynamic(4 () => import('@/components/EkooWidget').then(m => m.EkooWidget),5 { ssr: false }6)78export default function ProductPage() {9 return <EkooWidget websiteId="..." productId="..." />10}