Extending
Vivace's keys and triggers are both open sets. Everything below composes with the shipped grammar — custom keys take modifiers, custom triggers work on any composition.
Exit animations
Entrances are declarative, but leaving the DOM needs a promise: play the exit, then remove the element. Declare the exit composition in data-viv-out (default @fd-o) and await Vivace.out():
import Vivace from 'vivace-css'
// <div data-viv="@dr" data-viv-out="@fd-o @sl-y-o">…</div>
await Vivace.out(toast) // plays the exit, resolves when done
toast.remove() // fill-mode kept it hidden — no flashLifecycle events
Animated elements dispatch bubbling events you can hook anywhere:
| Event | Fires |
|---|---|
vivace:play | when an animation is (re)started |
vivace:out | when an exit composition starts |
vivace:end | when an animation finishes |
el.addEventListener('vivace:end', () => {
// bubbles — listen on the element, a container, or document
})Custom keys at runtime
defineKey() registers a new @key without touching SCSS. You fill the
same custom-property slots the shipped keys use — --ATX0..10 / --ATY* translate, --ASX*/--ASY* scale, --ARX/Y/Z* rotate, --AOP0/1 opacity, --AF0/1 filter:
import Vivace from 'vivace-css'
// '@wb' — a little wobble, usable immediately in any data-viv
Vivace.defineKey('@wb', {
keyframe: 10, // 2 = simple in/out, 10 = spring/oscillation
duration: 0.9, // --AD base seconds
vars: {
'--ARZ1': '8deg',
'--ARZ3': '-6deg',
'--ARZ5': '3deg',
'--ARZ7': '-1.5deg'
}
})Scale amplitudes with the spring table (var(--ASP0)…var(--ASP9))
and the intensity level (var(--AL)) so _lv-* modifiers keep working
on your key.
Custom triggers
defineTrigger() adds a new data-viv-on value. The setup runs once per
element; call fire() whenever the animation should play, and return a teardown for Vivace.destroy():
import Vivace from 'vivace-css'
// data-viv-on="longpress"
Vivace.defineTrigger('longpress', (el, fire) => {
let timer: ReturnType<typeof setTimeout>
const down = () => { timer = setTimeout(fire, 500) }
const up = () => clearTimeout(timer)
el.addEventListener('pointerdown', down)
el.addEventListener('pointerup', up)
return () => {
el.removeEventListener('pointerdown', down)
el.removeEventListener('pointerup', up)
}
})Register triggers before Vivace.init() so elements parsed on load pick them up.
Keys as SCSS plugins
For keys you ship with your bundle, write an SCSS plugin instead — one file in keys/, one @use line, and the engine needs zero registration:
// keys/_wobble.scss
@use '../config' as c;
@use '../mixins' as m;
@include m.key('@wb') {
--AN: #{c.$kf-10};
@for $i from 0 through 9 {
--ARZ#{$i}: calc(#{m.alt($i) * 20deg} * var(--ASP#{$i}));
}
}Custom bundles work the same way: write your own entry that @uses keyframes, base and only the key/modifier plugins you want.