Blog / Development
Interactive illustrations in React and Next.js
Embed snippets are written for plain HTML. React apps render on the client, change routes without reloading and may render on the server first. Here is how to use an interactive illustration embed cleanly in React and Next.js.
By Abdullah NomanOctober 16, 20263 min read

For still and animated illustrations in React, components are the natural fit, and we cover them in SVG illustrations in React and Next.js. Interactive figures from an embed script need a slightly different setup: the script owns what is drawn inside an element, and React owns the element. The trick is to keep those two jobs apart.
1. Load the script once
Next.js
Use next/script in your root layout. The Next.js docs list four strategies; for decorative figures, afterInteractive (the default) or lazyOnload both work. Avoid beforeInteractive, which is meant for critical scripts. In the Pages Router, put the same <Script> in pages/_app.tsx.
// app/layout.tsx
import Script from 'next/script'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script src="https://liniqo.com/live/embed.js" strategy="lazyOnload" />
</body>
</html>
)
}
Vite, Create React App and others
Add the script tag to index.html with async, or inject it once from your app entry:
if (!document.querySelector('script[src*="liniqo.com/live/embed.js"]')) {
const s = document.createElement('script')
s.src = 'https://liniqo.com/live/embed.js'
s.async = true
document.body.appendChild(s)
}
2. A small wrapper component
React renders an empty div with the right attributes; the script fills it. The wrapper gives it a 5:4 box so nothing shifts while it loads:
// components/LiveFigure.tsx
type Props = {
name: string
accent?: string
line?: string
bg?: string
theme?: 'light' | 'dark'
intensity?: number
label?: string
className?: string
}
export function LiveFigure({ name, accent, line, bg, theme, intensity, label, className }: Props) {
return (
<div className={className} style={{ width: '100%', aspectRatio: '5 / 4' }}>
<div
key={[name, accent, line, bg, theme, intensity].join('|')}
data-liniqo={name}
data-accent={accent}
data-line={line}
data-bg={bg}
data-theme={theme}
data-intensity={intensity}
{...(label ? { role: 'img', 'aria-label': label } : { 'aria-hidden': true })}
/>
</div>
)
}
Use it like any component:
<LiveFigure name="ci-pipeline" accent="#7c3aed" bg="#ffffff" />
3. Client routing just works
The Liniqo Live script watches the page for new elements. When React mounts a data-liniqo element after a route change, in a modal or in a list, the script picks it up and loads that figure when it nears the screen. You do not need to call anything after navigation.
4. Changing colours or theme
A figure reads its attributes when it is drawn. To apply new colours, React needs to give it a fresh element, which is what the key in the wrapper does: when any option changes, React replaces the inner div, and the script draws a new figure with the new values. Connect theme to your theme state and the figure follows your dark mode toggle:
const { resolvedTheme } = useTheme() // e.g. next-themes
<LiveFigure name="analytics-bars" theme={resolvedTheme === 'dark' ? 'dark' : 'light'}
bg={resolvedTheme === 'dark' ? '#0e1013' : '#ffffff'} />
Without data-theme, a figure also goes dark by itself when an ancestor has the class dark or data-theme="dark" at the moment it is drawn, which matches Tailwind's class-based dark mode.
5. Server rendering and hydration
The wrapper renders the same empty element on the server and the client, so there is nothing to mismatch. Two rules keep it that way:
- Do not render children inside the
data-liniqoelement. The script replaces its contents. - Load the script after hydration, which
afterInteractiveandlazyOnloaddo. A script that draws before React hydrates can produce hydration warnings.
6. TypeScript and lint notes
data-*attributes are valid on any JSX element; no type declarations are needed.- Store any licence token for Pro figures (the
data-keyin the copied embed code) in configuration rather than scattering it through components.
When to use components instead
If you want still or draw-on illustrations that live in your repo as code, use Liniqo's React components through the shadcn registry. Use the live embed for figures that respond to the pointer. Browse the 500 interactive figures in the library; 23 are free, and the rest come with Pro and Lifetime.
Questions
How do I add an external script in Next.js?
Use the Script component from next/script. The default strategy, afterInteractive, loads it after hydration; lazyOnload waits for browser idle time, which suits decorative embeds.
Do embedded figures work with React Router or Next.js client navigation?
Yes. The Liniqo Live script watches for new data-liniqo elements and mounts them when they appear, including after client-side route changes.
How do I change an embedded figure's colours from React state?
Give the element a key built from its options. When an option changes, React replaces the element and the script draws a new figure with the new attributes.
Will the embed cause hydration errors?
Not if the element renders without children and the script loads after hydration, as next/script's afterInteractive and lazyOnload strategies do.



