React / Next.js

Initialize once at the root in a useEffect:

tsx
// App.tsx
import { useEffect } from 'react'
import Vivace from 'vivace-css'
import 'vivace-css/vivace.css'

export default function App() {
  useEffect(() => {
    Vivace.init()
    return () => Vivace.destroy()
  }, [])

  return <Page />
}

init() is idempotent and destroy() is the cleanup, so React 18/19 StrictMode's double-invoke is harmless.

Annotate JSX with the same attributes — data-* passes straight through:

tsx
<h1 data-viv="@fd @sl-y_ease-out-expo">Hello</h1>

<ul data-viv="@fd_child-ascend" data-viv-on="appearing">
  {items.map((item) => (
    <li key={item.id}>{item.label}</li>
  ))}
</ul>

Next.js (App Router)

Effects only run on the client, so wrap the init in a small client component and render it once in the root layout:

tsx
// app/vivace-provider.tsx
'use client'

import { useEffect } from 'react'
import Vivace from 'vivace-css'

export function VivaceProvider() {
  useEffect(() => {
    Vivace.init()
    return () => Vivace.destroy()
  }, [])
  return null
}

// app/layout.tsx
import 'vivace-css/vivace.css'
import { VivaceProvider } from './vivace-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <VivaceProvider />
        {children}
      </body>
    </html>
  )
}

Re-renders & route changes

React replacing DOM nodes is exactly what the MutationObserver watches for — newly mounted elements register themselves, and unmounted ones release automatically (the registry holds them weakly). Conditional rendering, suspense boundaries and client-side navigation all just work.

Programmatic control

tsx
import { useRef } from 'react'
import Vivace from 'vivace-css'

function Card() {
  const ref = useRef<HTMLDivElement>(null)
  return (
    <>
      <div ref={ref} data-viv="@pop">…</div>
      <button onClick={() => ref.current && Vivace.trigger(ref.current)}>
        replay
      </button>
    </>
  )
}
Portrait of Axel RAKOTOARIVAO
Created by
Axel RAKOTOARIVAO
MIT — keyframe DSL inspired by A.css
bun add vivace-css