Animate on Scroll, reimagined.
A modern, zero-dependency AOS replacement with full TypeScript support.
Aos is a lightweight, TypeScript-first scroll animation library powered by IntersectionObserver. It's a drop-in replacement for the original AOS library β actively maintained, with more features and zero dependencies.
If you've used AOS before, you'll feel right at home. If you haven't, here's what you get:
- Zero dependencies β uses only the DOM and
IntersectionObserver - Full TypeScript support β fully typed config, callbacks, and return values
- Anchor support β trigger animations based on a different element's visibility
- Mirror mode β reverse animations when elements leave the viewport
- Per-element overrides β set
data-aos-duration,data-aos-easing, anddata-aos-delayon individual elements - Mobile control β disable animations on mobile devices or below a minimum window width
- Custom disable logic β pass your own function to decide whether to initialise
- Clean teardown β
destroy()disconnects all observers and removes all added classes/attributes
- Quickstart
- Configuration
- HTML attributes
- Anchors
- Callbacks
- Disabling animations
- Cleanup
- Framework examples
- API reference
- Contributor docs
- License
Install:
bun add @nitlix-hq/aosOther package managers:
pnpm add @nitlix-hq/aos
npm i @nitlix-hq/aosApply the data-aos attribute with the name of the animation you want:
<div data-aos="fade-up">I animate when scrolled into view</div>
<div data-aos="fade-left" data-aos-duration="500">I'm faster</div>You'll need to include the AOS stylesheet for the built-in animation classes. You can use the original AOS CSS or write your own β Aos toggles the aos-init and aos-animate classes, so any CSS that targets those will work.
import init from "@nitlix-hq/aos";
const aos = init({
duration: 800,
easing: "ease-in-out",
mirror: false,
});That's it. Every element with data-aos is now observed and will animate when it enters the viewport.
aos.destroy();Pass a config object to init(). All fields are optional:
| Option | Type | Default | Description |
|---|---|---|---|
duration |
number |
1000 |
Animation duration in milliseconds |
easing |
string |
"ease-in-out" |
CSS easing function |
delay |
number |
0 |
Delay before the animation starts (ms) |
mirror |
boolean |
false |
Reverse animations when elements scroll out of view |
offsetEnter |
number |
0 |
Offset (px) from the top of the viewport for triggering enter |
offsetExit |
number |
0 |
Offset (px) from the bottom of the viewport for triggering exit |
mobile |
boolean |
false |
Disable animations on mobile devices (ignored if disableInitFunc is set) |
minWindowWidth |
number |
0 |
Minimum window width for animations to be enabled (ignored if disableInitFunc is set) |
callback |
(element, status, observer, entry) => void |
β | Called when an element enters or exits the viewport |
disableInitFunc |
() => boolean |
β | Custom function to determine whether Aos should be disabled |
These attributes can be set on individual elements to override global config:
| Attribute | Description |
|---|---|
data-aos |
The animation name (required) |
data-aos-duration |
Override animation duration (ms) |
data-aos-easing |
Override CSS easing function |
data-aos-delay |
Override animation delay (ms) |
data-aos-anchor |
CSS selector of an anchor element |
If a per-element attribute is already set, Aos won't overwrite it with the global default.
By default, Aos observes the animated element itself. With data-aos-anchor, you can trigger an animation based on a different element entering the viewport:
<div id="trigger-point">Scroll past me...</div>
<div data-aos="fade-up" data-aos-anchor="#trigger-point">
I animate when #trigger-point is visible
</div>If the anchor selector is invalid or not found, Aos falls back to observing the element itself.
The callback option fires every time an observed element enters or exits the viewport:
init({
callback: (element, status, observer, entry) => {
console.log(element, status); // "enter" or "exit"
},
});Exit callbacks only fire when mirror is true.
init({ mobile: false }); // disables on mobile user agentsinit({ minWindowWidth: 768 }); // disables below 768pxinit({
disableInitFunc: () => {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
},
});When disabled, Aos strips all data-aos related attributes from elements so they render in their final state without animation.
init() returns an object with a destroy() method. Calling it:
- Disconnects all
IntersectionObserverinstances - Removes
aos-initandaos-animateclasses from all managed elements - Removes any attributes that Aos added (preserves attributes that were already present)
const aos = init();
// Later (e.g. on route change in an SPA):
aos.destroy();import { useEffect } from "react";
import init from "@nitlix-hq/aos";
function App() {
useEffect(() => {
const aos = init({ duration: 800, mirror: true });
return () => aos.destroy();
}, []);
return <div data-aos="fade-up">Hello</div>;
}<script>
import { onMount, onDestroy } from "svelte";
import init from "@nitlix-hq/aos";
let aos;
onMount(() => {
aos = init({ duration: 800 });
});
onDestroy(() => aos?.destroy());
</script>
<div data-aos="fade-up">Hello</div><script type="module">
import init from "@nitlix-hq/aos";
init({ duration: 1000, easing: "ease-out" });
</script>
<div data-aos="fade-up">Hello</div>Initialises Aos. Scans the DOM for [data-aos] elements, sets up IntersectionObserver instances, and applies default attributes.
Parameters:
| Name | Type | Description |
|---|---|---|
config |
Config |
Optional config object |
Returns: { destroy: () => void }
interface Config {
duration?: number;
easing?: string;
callback?: (
element: Element,
status: "enter" | "exit",
observer: IntersectionObserver,
entry: IntersectionObserverEntry,
) => void;
mirror?: boolean;
disableInitFunc?: () => boolean;
delay?: number;
offsetEnter?: number;
offsetExit?: number;
mobile?: boolean;
minWindowWidth?: number;
}- Implementation & extension guide:
FOR_AGENTS.md
MIT