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.
| Value | Intent | Engine effect |
|---|---|---|
standard | Balanced default feel | Neutral scale (1×), cinematic easing |
subtle | Quiet, fast, short travel | Faster duration, reduced travel & depth, soft easing |
cinematic | Grand, slow, deep, blurred entrances | Slower duration, larger travel & depth, entrance blur |
still | Author-driven reduced motion | Resting/composed state, no movement (independent of OS setting) |
Test here — compare modes
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.
| Token | Role | Neutral default |
|---|---|---|
--vl-motion-mode | Active mode (queried by if(style())) | standard |
--vl-motion-speed-scale | Duration multiplier | 1 |
--vl-motion-intensity | Travel-distance multiplier | 1 |
--vl-motion-depth | Parallax / z-depth multiplier | 1 |
--vl-motion-blur | Additive entrance blur | 0px |
--vl-motion-preference | Reflects reduced-motion intent | auto |
--vl-motion-mode-ease | Easing 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:
- Baseline (works everywhere): plain
[vl-motion="…"]attribute selectors assign the engine knobs. Noif()required. - Enhancement (
@supports+if()): a single[vl-motion]rule re-derives the same knobs using CSSif()withmedia(),style()andsupports(), folding in live conditions —prefers-reduced-motion,pointer: coarse, and scroll-timeline availability.
@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:
- Under
@media (prefers-reduced-motion: reduce)the engine knobs collapse (travel, depth and blur go to zero), and — as with every Velora effect — animations are disabled globally in03-motion.css. vl-motion="still"lets an author opt any subtree into a motionless resting state regardless of the OS setting.
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.