Skip to content
Velora
Showcase
Motion

Conditional Motion Engine

A single declarative attribute — vl-motion — selects a motion mode for an element and its subtree. It does not name a preset; instead it re-scales the shared engine tokens that every preset already reads, so one attribute adapts timing, travel, depth, blur and easing across the whole subtree. Zero animation runtime JavaScript.

The Modes

Add vl-motion to any element. Descendant vl-enter / vl-scrollreveals inherit the mode automatically.

ValueIntentEngine effect
standardBalanced default feelNeutral scale (1×), cinematic easing
subtleQuiet, fast, short travelFaster duration, reduced travel & depth, soft easing
cinematicGrand, slow, deep, blurred entrancesSlower duration, larger travel & depth, entrance blur
stillAuthor-driven reduced motionResting/composed state, no movement (independent of OS setting)

Test here — compare modes

Test here

Scroll each column into view.

cinematic

Grand & deep

subtle

Quiet & fast

<section vl-motion="cinematic|subtle">…</section>
<!-- One attribute governs the whole subtree -->
<section vl-motion="cinematic">
  <h2 vl-enter="reveal-cinematic" vl-timeline="view">Grand & deep</h2>
  <p vl-enter="fade-up" vl-timeline="view">Slower, deeper, blurred entrance.</p>
</section>

<section vl-motion="subtle">...quieter, faster motion...</section>
<section vl-motion="still">...reduced-motion-safe resting state...</section>

Engine Tokens

Modes work by assigning central --vl-* knobs (declared in 01-tokens.css). Presets consume these instead of hardcoding per-selector values, which keeps adaptation in one place.

TokenRoleNeutral default
--vl-motion-modeActive mode (queried by if(style()))standard
--vl-motion-speed-scaleDuration multiplier1
--vl-motion-intensityTravel-distance multiplier1
--vl-motion-depthParallax / z-depth multiplier1
--vl-motion-blurAdditive entrance blur0px
--vl-motion-preferenceReflects reduced-motion intentauto
--vl-motion-mode-easeEasing selected by the mode--vl-ease-cinematic

Because the defaults are neutral (scale 1, offset 0), motion behaves exactly as before when no vl-motion mode is present. Explicit per-element modifiers such asvl-speed and vl-depth still win over the mode.

Progressive Enhancement with CSS if()

The engine is CSS-first progressive enhancement. There are two layers:

@supports (width: if(style(--vl-probe: 1): 1px; else: 2px)) {
  [vl-motion] {
    --vl-motion-intensity: if(
      media(prefers-reduced-motion: reduce): 0;
      style(--vl-motion-mode: cinematic): if(media(pointer: coarse): 1.2; else: 1.6);
      style(--vl-motion-mode: subtle): 0.45;
      else: 1
    );
    --vl-motion-depth: if(
      supports(animation-timeline: view()): 1.8;   /* only where scroll timelines exist */
      else: 0
    );
  }
}

Fallback for browsers without if()

If a browser does not support CSS if(), the entire @supports block is ignored and the baseline [vl-motion="…"] values remain in effect. Modes still work — they simply do not receive the extra live pointer/support-aware refinements. Nothing breaks, and no JavaScript fallback is introduced.

Accessibility

Reduced motion is always respected:

Browser Support

The vl-motion modes themselves work in all modern browsers. The if()-based refinement requires a browser with CSS if() support; everywhere else the baseline modes are used. See the live comparison on theZero-JS Motion showcase page.