npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@awesome-reveal/astro

v1.0.0-next.11

Published

Reveal-on-scroll animations for Astro, built on the Web Animations API and IntersectionObserver.

Readme

@awesome-reveal/astro

Reveal-on-scroll animations for Astro. Wrap content in <Reveal>, and it fades and slides in when it enters the viewport. No framework, no hydration directive and no stylesheet import.

npm version License

Preview: This is a prerelease of version 1.0. The API can change before the stable release. Tell us what works and what does not in GitHub Discussions.

  • Simple. Two components and a few options.
  • Native. A small custom element, built on IntersectionObserver and the Web Animations API.
  • No flash. Server-rendered content stays hidden until it reveals.
  • Astro-ready. Works with the <ClientRouter />, server islands and transition:persist.
  • Accessible. Obeys the reduced-motion preference, and shows waiting content on focus and in print.

Works with Astro 6 and 7.

Install

npm install @awesome-reveal/astro

The package loads its stylesheet and a small client script automatically.

Quick start

---
import { Reveal } from "@awesome-reveal/astro";
---

<Reveal><h1>Hello</h1></Reveal>

The content fades in and moves up one time, when it enters the viewport.

Usage

Choose the entrance

from sets where the element starts. A side gives a fade and a 24 px movement. The default is "bottom".

<Reveal from="left">From the left</Reveal>

For a different entrance, give a start state. The element animates from this state to its resting style. A start state replaces the default, so add opacity: 0 if you want a fade.

<Reveal from={{ opacity: 0 }}>Fade only</Reveal>
<Reveal from={{ scale: 0.9 }}>Scale only</Reveal>
<Reveal from={{ opacity: 0, x: "-100%", rotate: "-5deg", blur: 8 }}>All together</Reveal>

A start state has the properties opacity, x, y, scale, rotate and blur. A number for x, y or blur is in pixels, and a number for rotate is in degrees. A string can contain a CSS value with its unit.

Use your own keyframes

For full control, such as a custom easing, give native keyframes instead of from. End them at the resting style of the element.

<Reveal
  keyframes={[
    { opacity: 0, transform: "scale(.8)", easing: "cubic-bezier(.34,1.56,.64,1)" },
    { opacity: 1, transform: "scale(1)" },
  ]}
>
  Bounce in
</Reveal>

Change the timing

<Reveal as="h2" from="left" duration={800}>Title</Reveal>
<Reveal from={{ opacity: 0 }} delay={200}>Starts after the title</Reveal>
<Reveal threshold={0.5}>Reveals when half of it is visible</Reveal>

Keep delay below about 300 ms. A long delay makes content appear late when the user scrolls quickly.

Reveal a list

Cascade reveals its direct children one after the other, when it enters the viewport. interval sets the time between two children, and the default is 80 ms.

---
import { Cascade } from "@awesome-reveal/astro";
---

<Cascade as="ul" from="left" interval={120}>
  <li>Fast</li>
  <li>Small</li>
</Cascade>

For a long list, or a grid that is taller than the viewport, wrap each item in Reveal instead. A cascade schedules all children at the same time, so the last children start late or out of view.

Control the reveal

when controls the entrance. true plays it, and false resets it with no exit animation. Without when, the viewport controls it. After the page loads, set the when property of the awesome-reveal host element.

when does not show or hide content. With when={false}, the element is transparent, but screen readers read it and the keyboard can focus it. inert does not hide content: it only blocks focus, clicks and assistive technology. To hide content, also set hidden on the rendered tag:

---
import { Reveal } from "@awesome-reveal/astro";
---

<button id="toggle" aria-expanded="false">Toggle</button>
<Reveal id="details" when={false} hidden from={{ opacity: 0 }}>Details</Reveal>

<script>
  import type {} from "@awesome-reveal/astro";

  const button = document.querySelector("#toggle")!;
  const details = document.querySelector("#details")!;
  const host = details.closest("awesome-reveal")!;

  button.addEventListener("click", () => {
    const open = !host.when;
    host.when = open;
    details.toggleAttribute("hidden", !open);
    button.setAttribute("aria-expanded", String(open));
  });
</script>

The type import adds the awesome-reveal element to the DOM types. To play the entrance again, set when to false, then to true.

Run code at the reveal

The rendered tag dispatches a reveal event that bubbles, when the reveal starts. The host has a revealed property. To wait for the end of the entrance, wait for the finished promises of the animations of the event target:

document.addEventListener("reveal", (event) => {
  const element = event.target as Element;
  Promise.all(element.getAnimations({ subtree: true }).map((animation) => animation.finished))
    .then(() => element.classList.add("entered"))
    .catch(() => {});
});

The promise rejects when the entrance stops early, for example after when becomes false. Catch the rejection. It resolves at once when no entrance plays, for example with reduced motion. It also waits for your own animations of the element and its descendants.

API

<Reveal> and <Cascade>

| Prop | Type | Default | Description | | --------------------------- | -------------------- | ---------- | --------------------------------------------------------------- | | as | RevealTag | "div" | The rendered tag | | from | Side \| StartState | "bottom" | The side or the start state of the entrance | | keyframes | Keyframe[] | — | Native keyframes. You cannot use them together with from | | duration | number | 600 | The duration of the entrance, in milliseconds | | delay | number | 0 | The time between the reveal and the entrance, in milliseconds | | threshold | number | 0 | The visible fraction, from 0 to 1, that reveals the element | | when | boolean \| null | — | true plays the entrance, false resets it | | interval (Cascade only) | number | 80 | The time between the entrances of two children, in milliseconds |

The components pass the other attributes and the children to the rendered tag.

The awesome-reveal host element

Each component renders its tag inside an awesome-reveal host element with display: contents.

| Member | Description | | -------------- | ------------------------------------------------------------------------- | | when | Gets or sets the manual control. null gives the control to the viewport | | revealed | Tells if the element is revealed | | reveal event | Comes from the rendered tag and bubbles, one time for each reveal |

Behavior

  • The entrance plays one time. The observation stops at the reveal.
  • The reveal event also fires when no entrance plays, for example with reduced motion or focus. After when becomes false, the next reveal fires it again.
  • A change to when or threshold applies immediately. A change to the other options applies at the next reveal.
  • when={false} during the delay resets the entrance, and the entrance does not play.

Where you can put a reveal

The HTML parser moves some tags out of the host element. Obey these rules:

  • as accepts an HTML tag or svg. TypeScript rejects document tags (html, head, body), table parts (caption, col, colgroup, thead, tbody, tfoot, tr, td, th) and SVG-only tags.

  • Do not put Reveal or Cascade directly in a <table>, a table section, a row or a <select>. Reveal the whole table, or reveal content inside a table cell:

    <Reveal as="table">
      <tbody>
        <tr><td>The whole table reveals together.</td></tr>
      </tbody>
    </Reveal>
  • In a <p>, also inline in MDX text, use an inline tag, such as as="span".

If the parser moves the tag, the host reports an error in the browser console, and the tag shows without an entrance after a few seconds.

Accessibility

  • With prefers-reduced-motion: reduce, all the content shows immediately, with no animation.
  • Focus in a waiting element shows it at once, so a keyboard user does not focus invisible content. This does not apply with when={false}.
  • Focus during a running entrance or its delay does not shorten it, so keep delay and interval short.
  • Waiting content shows in print and when JavaScript is off.
  • Hidden content stays in the accessibility tree. Do not make important content depend on decorative motion.

Good to know

  • The cascade reads its children when it reveals. A child that you add after the reveal shows with no animation.
  • Only the direct element children animate. Text and children with display: contents show immediately.
  • A framework island and the host of a nested Reveal have display: contents, so the cascade does not animate them. A nested Reveal plays only its own entrance. To put an island in the cascade, wrap it in an element.
  • delay moves the start of the whole sequence. The reveal event comes from the rendered tag.
  • Reveals work after a <ClientRouter /> navigation, in server islands and with transition:persist.
  • A prerendered page does not animate until the browser activates it.
  • The server markup has data-awesome-reveal="hidden", or data-awesome-reveal="cascade" for a cascade.
  • The stylesheet hides content for at most 5 seconds before the client script attaches the element. If the script fails, the content shows. If the attach happens later, for example on a slow network, the content shows, then hides again until it reveals.
  • The stylesheet hides with the awesome-reveal-hold CSS animation, which replaces your own animation on a waiting element until it reveals. Put your own animation on an inner element. The hold fires animationstart and animationend events, and shows in getAnimations().
  • An inline opacity with !important, or your own animation with !important, can show the element before the reveal.
  • The stylesheet does not reach a shadow root.
  • In browsers without the scripting media feature, such as Safari 16, content shows after 5 seconds when JavaScript is off.

The package writes no style attribute, no inline script and no CSS at runtime. But Astro can put the package stylesheet and your scripts inline in the HTML. Use one of these setups:

  • Set security.csp: true. Astro adds a <meta> policy with hashes for the inline styles and scripts that it emits. Astro adds the policy only to the build, so use astro build and astro preview to check it. Astro does not support this option with the <ClientRouter />.
  • Send your own policy, such as style-src 'self'; script-src 'self', and set build.inlineStylesheets: 'never'. With the default 'auto', Astro puts stylesheets smaller than vite.build.assetsInlineLimit (4 kB by default) in a <style> element. If the policy blocks the package stylesheet, the content does not hide before the reveal, and the awesome-reveal host is not display: contents. Also make sure that the built HTML has no inline <script> that your policy blocks.

To reveal content inside a React or Vue island, use @awesome-reveal/react or @awesome-reveal/vue in the island, and import its reveal.css in the page. Hydrate the island eagerly, for example with client:load.

The @awesome-reveal/core README describes how the entrance works, the thresholds of tall and clipped content, performance and errors. Read it on npm.

Agent skills

The package includes the adding-reveal-animations skill, which teaches a coding agent this API. pnpm can link it into your project after you approve it.

License

MIT