@radiobros/animatio
v1.0.0
Published
CSS-only animation + effects library. animate.css parity, composable transform engine, scroll-driven reveals, glass/backdrop surfaces, and a tree-shaking engine.
Maintainers
Readme
Animatio
A CSS-only animation and effects library. 97 classic presets, a composable transform engine, scroll-driven reveals with an honest support ladder, glass surfaces, 392 classes — and a tree-shaking engine so you ship only what you use.
npm i @radiobros/animatio| | animate.css | AOS | Tailwind anim. | Animatio |
|---|---|---|---|---|
| animate.style catalogue | ✅ 97 | ❌ | ❌ | ✅ 97 |
| CSS-only (zero JS runtime) | ✅ | ❌ | ✅ | ✅ |
| Stackable effects (fade + up) | ❌ | ❌ | ❌ | ✅ |
| Per-element config from HTML | partial | data-attrs (JS) | ❌ | ✅ 4 tiers |
| Scroll-driven (view() / scroll()) | ❌ | JS | ❌ | ✅ + native triggers |
| Glass / backdrop / SVG effects | ❌ | ❌ | ❌ | ✅ |
| prefers-reduced-motion | partial | ❌ | ❌ | ✅ every effect |
| Ships only what you use | ❌ | ❌ | ✅ | ✅ |
20.6 KB gzip for everything. 3.3 KB for a real site, tree-shaken.
Quick start
<link rel="stylesheet" href="node_modules/animatio/dist/animatio.min.css">
<div class="an an-fadeInUp">Hello</div>Tree-shaken, with a bundler:
// vite.config.js
import animatio from "@radiobros/animatio/vite";
export default { plugins: [animatio({ content: ["src/**/*.{vue,html}"] })] };import "@radiobros/animatio"; // resolves to just the classes your content usesNo bundler? The CLI covers Rails, Laravel, WordPress and plain HTML:
npx animatio init
npx animatio build --watchThe four ways to configure an effect
Every value is expressible in all of tiers 1–3, in every browser.
<!-- 1. inline custom properties — arbitrary values, universal -->
<div class="an an-fadeInUp" style="--an-duration: 1.2s; --an-distance: 4rem"></div>
<!-- 2. data attributes — presets, universal -->
<div class="an an-fadeInUp" data-an-speed="slow" data-an-ease="spring"></div>
<!-- 3. utility classes — the familiar names -->
<div class="an an-fadeInUp an-slow an-delay-2s"></div>
<!-- 4. typed attr() — nicest ergonomics, Chrome 133+ -->
<div class="an an-fadeInUp" data-an-duration="1.4s" data-an-angle="12deg"></div>Composable atoms
A monolithic effect cannot stack with another: two that both write transform, so one
silently wins. Animatio's atoms each animate a single @property-registered
channel, so they compose.
<div class="an an-fade an-up an-scale-in"></div>Presets for entrances, atoms for choreography. Do not mix the two on one element — the dev-mode guard warns if you do.
Scroll
<div class="an-reveal">
<div class="an an-fadeInUp">Fires once when it enters</div>
</div>
<div class="an-scrub-up">Progress follows the scrollbar</div>an-reveal resolves through three tiers — native animation-trigger
(Chrome 145+), a style-query latch (Chrome 115+/Safari 26+), or plain visible
content. It never hides anything it cannot show again, which is why Firefox
stable renders a finished, readable page rather than a blank one.
Glass
<nav class="an-glass an-noise">…</nav>Grain is a real feTurbulence, delivered as a data-URI image so it works
everywhere.
What this library does not do
Published up front, because every competitor hides it and users find out in production.
| Want | Why CSS cannot | What ships instead |
|---|---|---|
| Cursor-following spotlight, magnetic pull | No pointer coordinates in CSS | -static and -zones variants, plus a 6-line coordinate feed |
| Text scramble | CSS cannot author text content | an-text-flip-chars |
| Per-character animation without spans | No ::nth-letter() | Split snippets + --an-i |
| Velocity-reactive physics | No runtime state | linear() spring curves |
| Particles, canvas, WebGL | Different rendering model | Nothing. Use three.js. |
| Scroll once in Firefox today | CSS cannot latch a scrubbed timeline | The tier ladder, plus an optional 12-line enhancer |
| SVG refraction in Safari | webkit #245510 | an-glass + an opt-in refract path |
backdrop-filter: url(#svg) is broken, not merely unsupported — WebKit
parses it and renders nothing. @supports therefore returns true in Safari
and would switch the broken path on, so refraction is gated on an explicit
<html data-an-refract="on"> after your own render probe.
an-svg-goo needs assets/animatio-filters.svg pasted into your
page once. The prefix is the install instruction, and check:svg-naming
enforces it exceptionlessly.
Accessibility
Not bolted on. The prefers-reduced-motion block is generated from the
compiled CSS, so every one of the 272 animating selectors has a treatment and
a new effect cannot ship without one:
- decorative motion → 1 ms
- ambient loops → paused
- flashing (glitch, CRT, VHS) → removed outright, WCAG 2.3.1
- scroll-linked → unbound from the timeline
- loaders keep running — WCAG 2.3.3 exempts essential motion, and a frozen spinner reads as a hung page
Plus prefers-reduced-transparency and prefers-contrast for the glass module.
Development
npm run verify # manifest -> build -> catalogue -> gates -> size -> tests
npm run build
npm run check # 16 CI gates
npm test # 33 testsEvery gate exists because the project makes a promise that would otherwise decay
silently: no animation shorthand (it resets animation-timeline), no hidden
state outside @supports, no two reveal tiers true at once, no unannotated
bleeding-edge feature, no animating class without an a11y treatment, no
near-duplicate class names, and the animate.css attribution present in every
bundle.
PLAN.md is the full design document — 24 sections covering every decision,
its trade-off, and the failure it prevents.
Status
Built through P7 of the plan: foundation, parity, props, atoms, scroll,
surfaces, the fx catalogue, tree-shaking, accessibility and packaging are done
and verified. The marketing site is in docs/. Visual-regression and
real-device passes (P8) are not yet run, so this is 0.1.0, not 1.0.0.
License
MIT. The parity module is derived from animate.css 4.1.1, Copyright (c) 2020 Animate.css, also MIT — see NOTICES for the full attribution.
Working on the docs site
npm run docs:build
npm run docs:verify # preview on a fresh port, prints cache-busted URLsUse vitepress preview, never serve -s. SPA mode rewrites every URL to
index.html, so each docs page boots with the landing page's HTML, hydrates
into a mismatch, and loses its sidebar. That looks exactly like a theme bug and
is not one. docs:verify picks a fresh port and prints cache-busted URLs,
because a stale stylesheet after a rebuild makes an already-fixed problem read
as unfixed.
