Drop-in web components with spring-physics animations. Works in any framework - or no framework at all.
Architected with animation as a core concern from day one - not bolted on as an afterthought. Every component leverages spring physics, interruptible animations, and composable primitives to deliver interactions that feel natural and responsive.
βΈ Reveal elements on scroll ββΈ Hover/press/tilt responses ββΈ Character-by-character text effects ββΈ Parallax & scroll scenes ββΈ Sliders, dialogs, image comparisons & more
Built with Motion and Lit. Full TypeScript types included.
1. Install
npm install motion-components@^12. Import & use
// Import all components
import 'motion-components'
// Import separate components (tree shakable)
import 'motion-components/motion-reveal'
import 'motion-components/motion-hover'
import 'motion-components/motion-stagger'
// Prevent content flash before animations run
import 'motion-components/preload.css'<motion-reveal>
<h1>Animates in when scrolled into view</h1>
</motion-reveal>
<motion-hover scale="1.05">
<button>Hover me</button>
</motion-hover>
<motion-stagger interval="0.06">
<p>First child</p>
<p>Second child</p>
<p>Third child</p>
</motion-stagger><script type="module" src="https://cdn.jsdelivr.net/npm/motion-components@1/+esm"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/motion-components@1/dist/preload.css" />
<motion-reveal>
<h1>Animates in when scrolled into view</h1>
</motion-reveal>require('motion-components') works on Node 20.19+, 22.12+ and 23+. Jest needs ES module support turned on.
| Framework | Config needed? |
|---|---|
| Astro, Svelte, Solid, Preact | None |
| React | Use React 19+ (native web component support). React 18 has limited support - upgrade if possible. |
| Vue 3 | Tell the compiler to treat motion-* as custom elements |
| Angular | Add CUSTOM_ELEMENTS_SCHEMA |
| Plain HTML | Use the CDN script tag above |
Vue 3 config
// vite.config.js
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('motion-'),
},
},
})Angular config
import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core'
@Component({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class MyComponent {}Reveal π
Entrance & transition effects
motion-reveal motion-stagger motion-blur motion-blur-in
Respond π
Input-driven interactivity
motion-hover motion-press motion-magnetic motion-tilt
Text π
Typography & character effects
motion-split motion-typewriter motion-counter motion-scramble
motion-ticker motion-words motion-curve motion-circle
motion-arc motion-headline motion-glitch motion-gravity
motion-liquid motion-perspective motion-stretch motion-swap
motion-text-mask motion-font motion-marker motion-underline
motion-strike motion-ring
Icons π
Animated and state-morphing icons
Scroll π
Scroll-driven animation
Components π
Ready-made interactive components
motion-slider motion-gallery motion-dialog motion-countdown
motion-spotlight motion-progress motion-image-compare motion-flip-card
motion-theme-toggle motion-theme-icon
Charts π
Animated bar, line, pie and donut charts
motion-chart motion-pie motion-sparkline
Code π
Syntax-highlighted code display
motion-code motion-code-inline
Every animation component implements the same playback interface, so you can drive animations imperatively:
const el = document.querySelector('motion-typewriter')
await el.play() // start if idle/finished, resume if paused
el.pause() // freeze in place
el.finish() // jump to the end state
el.cancel() // reset to the initial state
el.playState // 'idle' | 'running' | 'paused' | 'finished'
await el.finished // per-run promise, resolves on finish or cancelComponents emit motion-start, motion-finish, and motion-cancel events (bubbling and composed), so you can also listen at any ancestor:
document.addEventListener('motion-finish', (e) => console.log(e.target, 'done'))Input-reactive components (motion-hover, motion-press, motion-magnetic, motion-tilt) expose a disabled property/attribute instead β set it to make them settle and stop responding to input.
import { pauseAll, resumeAll, cancelAll } from 'motion-components'
pauseAll() // pause every running animation, disable input-reactive components
resumeAll() // resume what pauseAll paused, re-enable what it disabled
cancelAll() // reset everything to its initial state
pauseAll(sidebar) // all three accept a root node to scope the effectWhen prefers-reduced-motion: reduce is set, play() skips straight to the final state β the finished promise and events still fire, so control flow keeps working.
Web components upgrade asynchronously, so content can flash before animations are ready. Choose your prevention method:
import 'motion-components/preload.css'<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/motion-components@1/dist/preload.css" />import { preload } from 'motion-components'
preload(['motion-reveal', 'motion-split', 'motion-dialog'])Call preload() with no arguments to cover all preload-registered components.
import { preloadCSS } from 'motion-components'
// inject preloadCSS into your framework's <head> mechanismWhich components need preloading? Only those that hide content on initialization - reveal, text, stagger, dialog, and a few widgets. The build validates preload entries automatically via npm run check:preload.
AI assistants write better code from the current docs than from what they learned in training. Point yours at:
- llms.txt: every component, docs page and recipe, with one line each
- llms-full.txt: every attribute, event, slot and CSS custom property in one file
- Any docs page as Markdown: add
.mdto its address, for example motion-marker.md
The package also ships editor data in dist/: custom-elements.json, VS Code custom data and web-types.json for JetBrains IDEs. Using with AI has a rules block to paste into AGENTS.md or CLAUDE.md, and the editor setup.
- Motion-first. Built around animation from the start, not retrofitted.
- Spring physics by default. No linear easing, no jank.
- Interruptible. Interactions never queue or stutter - even mid-animation.
- Per-component imports. Each component is a standalone subpath export - no dead weight, no bundler magic required.
- Composable. Shared primitives instead of reimplemented animations.
- Accessible. Honors
prefers-reduced-motion. Keyboard-navigable.
npm install # install dependencies
npm run build # build library to ./dist
npm run dev # rebuild on file change
npm test # browser tests
npm run typecheck # tsc --noEmit
npm run lint # ESLint
npm run format # Prettier
npm run check:preload # validate FOUC preload rules
npm run check:exports # validate subpath exports
npm run check:ssr # import every entry in Node
npm run size # bundle size budget checksrc/
βββ reveal/ entrance & transition effects
βββ respond/ input-driven interactivity
βββ text/ typography effects
βββ icons/ animated and state-morphing icons
βββ scroll/ scroll-driven components
βββ components/ ready-made widgets
βββ charts/ data visualization
βββ code/ code-display components
βββ utils/ shared helpers
βββ test/ shared test helpers
See CONTRIBUTING.md for component authoring conventions.
Showcase π Β· Parallax π Β· Scene π Β· Motion Font π
MIT Β© Tanja Gomilar