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-ekooReq
string
Your Ekoo website UUID. Found in the backoffice → Site settings.
data-ekoo-product-idReq
string
Product reference (must match exactly what is in your Ekoo catalog).
data-ekoo-locale
string·default: auto
Locale 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-variant
string
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-id
string
ID of a specific audio review. If omitted, the first published review is used.
data-ekoo-on-event
string (fn name)
Name of a global window function called on each widget event (printed, played-0, played-25…).
Appearance — backoffice overrides
data-ekoo-direction
normal | reverse·default: normal
Expansion direction. normal = left-to-right, reverse = right-to-left.
data-ekoo-scale
number·default: 1
Scale factor (e.g. "1.2" for 20% larger).
data-ekoo-animation
string·default: pulse
Animation type for the widget icon at rest.
data-ekoo-animation-duration
string·default: continuous
Animation duration.
data-ekoo-always-open
boolean·default: false
If "true", the widget stays permanently expanded.
data-ekoo-show-image
boolean·default: true
Show or hide the product image in the widget.
data-ekoo-not-fully-clickable
boolean·default: false
If "true", only the play button is clickable.
data-ekoo-autoplay
boolean·default: false
Automatically start audio playback on load.
data-ekoo-show-transcript
boolean·default: false
Show a button to read the audio transcript.
data-ekoo-show-speed-button
boolean·default: false
Show a playback speed control.
data-ekoo-closed-state-main-text
string
Main CTA text shown when the widget is collapsed.
data-ekoo-closed-state-secondary-text
string
Secondary text below the CTA when the widget is collapsed.
SPA & Shadow DOM
data-ekoo-mode
spa | static·default: auto
Forces rendering mode. Auto-detected (Next.js, Nuxt, React, Vue, Angular, Sapper). Only use if auto-detection fails.
data-shadow-mode
open | 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 = true
Forces 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?: () => void
4 ekooUnload?: () => void
5 ekooReload?: () => void
6 }
7}
8
9export {}

2. EkooWidget Component

components/EkooWidget.tsx
tsx
1'use client' // Next.js App Router only
2
3import { useEffect, useRef } from 'react'
4
5const EKOO_SCRIPT_URL = 'https://app.ekoo.co/widgets/widget-4.0.0-standalone.js'
6
7interface EkooWidgetProps {
8 websiteId: string
9 productId: string
10 locale?: string
11}
12
13export function EkooWidget({
14 websiteId,
15 productId,
16 locale = 'fr',
17}: EkooWidgetProps) {
18 const containerRef = useRef<HTMLDivElement>(null)
19
20 useEffect(() => {
21 // Load the script if not already present
22 let script = document.querySelector<HTMLScriptElement>(
23 `script[src="${EKOO_SCRIPT_URL}"]`
24 )
25
26 if (!script) {
27 script = document.createElement('script')
28 script.src = EKOO_SCRIPT_URL
29 script.defer = true
30 document.head.appendChild(script)
31 }
32
33 // Initialize the widget once the script is loaded
34 const init = () => window.ekooLoad?.()
35
36 if (script.dataset.loaded === 'true') {
37 init()
38 } else {
39 script.addEventListener('load', () => {
40 script!.dataset.loaded = 'true'
41 init()
42 })
43 }
44
45 // Cleanup on unmount
46 return () => {
47 window.ekooUnload?.()
48 }
49 }, [websiteId, productId, locale])
50
51 return (
52 <div
53 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'
2
3export default function ProductPage({ params }: { params: { id: string } }) {
4 return (
5 <main>
6 <h1>My product</h1>
7 <EkooWidget
8 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 encounter window is not defined errors.
  • The Ekoo script manipulates the DOM — it must run on the client side only.
Dynamic import (Pages Router)
tsx
1import dynamic from 'next/dynamic'
2
3const EkooWidget = dynamic(
4 () => import('@/components/EkooWidget').then(m => m.EkooWidget),
5 { ssr: false }
6)
7
8export default function ProductPage() {
9 return <EkooWidget websiteId="..." productId="..." />
10}

Go Further

React — Documentation — Ekoo