I resurrected an 8-year-old dead CSS library and gave it an engine

axelrevana1 pts0 comments

Vivace | animate with attributes

Animate with attributes. Nothing else.<br>Compose entrances, springs and staggers directly in your markup. A tiny engine handles when — scroll, hover, click, focus — and pure CSS handles how.<br>Open playground Read the docs<br>bun npm pnpm yarn<br>$ bun add vivace-css copy

"@pr-i_child-ascend_ease-out-back @fd @sc-i!"

Gather

18<br>animation keys

trigger types

dependencies

gzipped, CSS + JS

Eighteen keys. Hover to feel them.<br>full gallery<br>@fd fade@sl slide@rt rotate@sc scale@sp spiral@vb vibrate@pr perspective@bn bounce@sh shake@fl flick@bl blur@sw swing@pop pop@dr drop@sq squash@hb heartbeat@wv wave@gl glow

Two steps. No wrappers, no keyframes.<br>1 Initialize once

One call at your app's root. A MutationObserver keeps watching, so anything your<br>framework mounts later registers itself.<br>ts copy<br>import Vivace from 'vivace-css'<br>import 'vivace-css/vivace.css'

Vivace.init()

2 Write attributes

Keys (@) say what plays, modifiers (_) tune it, data-viv-on says when. Everything composes.<br>html copy

Every list item<br>slides up in<br>sequence. That's it.

Every list item<br>slides up in<br>sequence. That's it.

Small grammar, endless compositions<br>Keys stack in one attribute; modifiers concatenate onto the key they tune. Click a card to<br>replay it, or open it in the playground.

Entrance remix<br>@fd @sl-y_ease-out-expo fade + slide, expo settle

Playful remix<br>@pop_ease-out-back elastic pop with overshoot

Dramatic remix<br>@pr-i_ease-out-expo @fd @rt-y-i flies in from deep Z, folding

Real triggers, zero listeners per element<br>load, appearing, hover, click and focus — one shared IntersectionObserver and a handful of delegated<br>listeners for the whole page, no matter how many elements animate.<br>trigger reference<br>click me hover me<br>scrolled in

Beyond entrances<br>Exit animations that resolve as promises, bubbling lifecycle events<br>(vivace:play, vivace:end), and a plugin API for your own<br>keys and triggers — at runtime or as SCSS plugins.<br>await Vivace.out(el) — remove without a flash<br>events for chaining logic off animations<br>defineKey / defineTrigger, one-file SCSS plugins<br>prefers-reduced-motion built in — content never hides

extending vivace<br>ts copy<br>// exits that wait for themselves<br>await Vivace.out(toast) // plays data-viv-out, resolves when done<br>toast.remove() // fill-mode kept it hidden — no flash

// your own keys and triggers<br>Vivace.defineKey('@wb', { keyframe: 10, vars: { '--ARZ1': '8deg' } })<br>Vivace.defineTrigger('longpress', (el, fire) => { /* … */ })

Works where your markup lives<br>Attributes pass through every renderer, and dynamically mounted elements register<br>themselves. No wrapper components to import, ever.<br>HTML Svelte React Vue

Compose one in ten seconds<br>Chain tokens in the playground, watch it live on real page sections, copy the attribute<br>out — or ship it to the community gallery.<br>Try the playground Browse the gallery

vivace keys copy attributes hover click

Related Articles