An animation governor: wraps a region (or the whole page) and gives it a motion budget, the share of the screen allowed to move at once (25% by default). Four times a second it measures the visible animated area (CSS and Web Animations, components' canvas loops); over budget, it slows down or pauses the endless decorative animations, lowest priority and largest first, while animations triggered by a click or a key press always run at full speed. Visitor dial Calm ↔ Lively that also shrinks amplitudes, a “Pause all motion” toggle (WCAG 2.2.2), a live meter and a debug mode that labels every moving element.
Maximum share of the viewport moving at the same time while the dial is on “Moderate”. Accepts “0.2” or “20%”.
mode
slow | pause
slow
What happens to a decorative animation over budget: slowed down (speed × 0.35) or paused, then released one second after the budget frees up.
controls
floating | inline
Shows the visitor dial. No value or floating: floating panel stuck to the bottom of the region (bottom right of the screen with target="document"); inline: panel at the top of the region.
debug
boolean
false
Outlines every moving element with its verdict (“decorative · slowed”, “feedback · allowed”, “transient · tolerated”…) and its share of the screen.
persist
string
Remembers the dial and the pause in localStorage under this key (empty value: the element's id or the page path).
target
region | document
region
Governs the element's content, or the whole page. In document mode, pausing also sets data-motion="reduce" on <html>, which stops every frameLoop() of the library.
level
number (0–1)
0.5
Initial dial: 0 = Calm (zero budget, amplitudes at 0), 0.5 = Moderate (the budget), 1 = Lively (no limit). Ignored when the visitor already chose (persist); 0 when the system asks for reduced motion.
data-motion-priority
number | essential
0
On an animated element or an ancestor: lower priorities are throttled first; essential is never throttled (essential motion in the WCAG sense).
data-motion-feedback
boolean
On an element or an ancestor: its animations are always treated as feedback.
data-motion-source
"" | speed | none
Declares a requestAnimationFrame loop the Web Animations API cannot see (canvas…). speed: the speed attribute is a speed multiplier (mv-marquee, mv-particles, mv-waves…) and will be scaled down; empty: only the mv-motion-throttle event is sent; none: ignore. Library components with a paused attribute (shaders, mv-circular-text, mv-morphing-text…) are detected automatically.
data-motion-throttle
slow | pause
Set by the component on a throttled source (read-only, styling hook).
Properties
Name
Type
Description
usage
number
Share of the viewport moving right now (0–1), weighted by speed: a slowed animation counts for 0.35, a paused one for 0.
demand
number
Share that would move without the governor.
throttled
{ target, mode, area, kind }[]
Elements currently slowed or paused (kind: animation or source).
level
number
Current dial (0–1), writable.
paused
boolean
True while everything is paused (read-only, see pauseAll).
Methods
Name
Description
pauseAll() / resumeAll()
Pauses (or resumes) every endless animation and loop in the region, including those that appear later.
setLevel(level)
Sets the dial from Calm (0) to Lively (1): the effective budget and --mv-motion follow, smoothly.
Events
Name
Description
mv-motion-budget
On the element, on every sample where usage moves by at least 1 point, the budget changes or the set of throttled elements changes. detail: { usage, demand, budget, throttled }.
mv-motion-throttle
On a source (data-motion-source or detected component), bubbles: detail { mode: "slow" | "pause" | null, rate }. null = released. Listen to it to throttle a custom loop.
Content structure
Name
Description
(content)
The governed region: any animated content, including content added later.
CSS classes
Name
Description
mv-motion-budget-panel
Control panel (role=group): dial, meter, pause button.
mv-motion-budget-range / -spark / -pause
Native slider, meter sparkline and toggle button.
mv-motion-budget-dock
Sticky row carrying the floating panel.
mv-motion-budget-box / -tag
Debug-mode outlines and labels (data-kind, data-state, data-place).
CSS variables
Name
Description
--mv-motion
Set on the region (or <html>) from the dial: 1 from Moderate up, down to 0 at Calm, with a smooth transition. Multiply your amplitudes by it.
--mv-motion-budget-amp
Registered property (@property) that carries the --mv-motion transition.
Accessibility
The dial is a real <input type="range"> named by the visible “Animations” title (arrows, Home/End, Page Up/Down), with aria-valuetext such as “Moderate: up to 20% of the screen in motion” and the meter as its description. “Pause all motion” is a toggle button (aria-pressed) with a constant label that meets WCAG 2.2.2 (Pause, Stop, Hide): every endless animation and loop stops, new ones too, and data-motion="reduce" is set on the region so library components switch to their static mode. The meter is an aria-hidden chart backed by visible text (“Motion on screen 18%”, “Budget 20% · 3 slowed”); no live region chatters four times a second. When the system asks for reduced motion, the dial starts at Calm. Nothing traps focus; the panel is neither modal nor obscuring. Feedback (animations starting within 400 ms of a click or key press) is never throttled, so visitors always see the effect of their action. Cost: 4 Hz sampling (setInterval, no rAF loop), suspended while the tab is hidden or the region is off screen; areas read from an IntersectionObserver (getBoundingClientRect only in debug mode); everything is restored when the element is removed.