Skip to content
L Lorenzota
astro-rocket animations components customization css

Animations in Astro Rocket — Every Effect Explained

A complete breakdown of every animation in Astro Rocket — page transitions, scroll reveals, the reactive floating header, hero entrance, Lighthouse score arcs, and the micro-animation library.

H

Hans Martens

3 min read

Astro Rocket ships with animations on every page. Not decorative noise — purposeful motion that makes the site feel fast, polished, and alive. This post breaks down every animation in the theme: what it does, where it lives, and how to tune or disable it.

All animations in Astro Rocket respect the prefers-reduced-motion media query. Users who have enabled reduced motion in their operating system preferences will see no micro-animations. The implementation is in src/styles/global.css and requires no extra work on your part.

Page transitions

The most noticeable animation is the one between pages. Astro Rocket uses Astro’s built-in <ClientRouter /> component, which leverages the browser’s View Transitions API to animate from one page to the next.

Instead of a hard reload, the page content slides up and out while the new page slides up into view — a smooth, app-like transition that makes navigation feel immediate. This is enabled globally in src/layouts/BaseLayout.astro:

import { ClientRouter } from 'astro:transitions';

<!-- inside <head> -->
<ClientRouter />

That single line is all it takes. Every internal link in the site benefits from it automatically. No per-page configuration, no JavaScript hydration overhead.

Astro also supports fade and none transitions per element via transition:animate, and you can assign persistent elements a transition:name so they morph in place rather than slide out and back in — useful for shared headers, logos, or images that appear on multiple pages.

Scroll-triggered reveals

Any element with a data-reveal attribute fades in when it scrolls into view. The reveal system is built into src/layouts/BaseLayout.astro and works on every page without any extra setup.

The animation is a CSS opacity transition driven by an IntersectionObserver:

[data-reveal] {
  opacity: 0;
  transition: opacity 0.6s cubic-bezier(0, 0, 0.2, 1);
}

[data-reveal].is-visible {
  opacity: 1;
}

When an element reaches 12% visibility (threshold: 0.12), the observer adds the .is-visible class and unobserves — the element will not re-animate if the user scrolls back up.

Elements that are already in the viewport when the page loads are revealed immediately, with transition: none applied momentarily so they appear without animating.

Staggered reveals

You can delay a reveal relative to others using the data-reveal-delay attribute:

<div data-reveal>First — reveals immediately</div>
<div data-reveal data-reveal-delay="1">Second — 100ms delay</div>
<div data-reveal data-reveal-delay="2">Third — 200ms delay</div>
<div data-reveal data-reveal-delay="3">Fourth — 300ms delay</div>

The delays are defined in global.css:

[data-reveal][data-reveal-delay="1"] { transition-delay: 100ms; }
[data-reveal][data-reveal-delay="2"] { transition-delay: 200ms; }
[data-reveal][data-reveal-delay="3"] { transition-delay: 300ms; }

Eager reveal mode

Adding data-eager-reveal to the <body> element expands the observer’s root margin to 0px 0px 200px 0px, triggering reveals 200px before the element enters the viewport. This gives a more “loaded” feel to content-heavy pages where you want sections to be visible before the user reaches them.

Scroll-triggered counter animation

Any element with a data-countup attribute will animate its number from zero to the target value when it scrolls into view. The animation is wired into src/pages/index.astro.

The observer fires when the element reaches 40% visibility. Each counter runs for 1,200ms with a cubic ease-out curve:

const eased = 1 - Math.pow(1 - progress, 3);
el.textContent = Math.round(eased * target) + suffix;

After completing, the observer disconnects — scrolling back up and down does not re-trigger it.

To add a counter, give any element a data-countup attribute with the target number and an optional data-suffix:

<p data-countup="50" data-suffix="+">50+</p>

Hero entrance animation

The hero section uses its own entrance animation, separate from the page transition. Any element with the .animate-hero-slide-up class plays:

@keyframes hero-slide-up {
  from { transform: translateY(28px); }
  to   { transform: translateY(0); }
}

.animate-hero-slide-up {
  animation: hero-slide-up 0.7s cubic-bezier(0, 0, 0.2, 1) both;
}

The 28px upward travel is more dramatic than the standard slide-up (10px), so hero content feels like it’s emerging from below the fold rather than just fading in.

Scroll indicator

When showScrollIndicator is enabled on the Hero component, a chevron pair appears at the bottom of the section with its own two-phase animation:

  1. Fade-in: the indicator appears 1.4 seconds after page load — after the hero content has settled — with a 600ms ease-out transition from translateY(6px).
  2. Bounce loop: two chevrons oscillate with a 5px vertical travel on a 2-second ease-in-out loop. The second chevron starts 150ms later, creating a cascading wave.

The indicator hides with an opacity transition when the user scrolls more than 50px.

Scroll-reactive header

The floating header changes its appearance as the user scrolls. A scroll event listener in src/components/layout/Header.astro tracks the scroll position against a 60px threshold.

When the page scrolls past 60px, the header receives a data-scrolled attribute. CSS transitions on the header element then animate the background, border, and height changes:

const SCROLL_THRESHOLD = 60;

if (window.scrollY > SCROLL_THRESHOLD) {
  header.setAttribute('data-scrolled', '');
} else {
  header.removeAttribute('data-scrolled');
}

Height shrink

The floating header’s inner container shrinks from 56px to 48px on scroll:

[data-header-shape="floating"] .hdr-inner {
  transition: height 300ms ease, box-shadow 300ms;
}

[data-header-shape="floating"][data-scrolled] .hdr-inner {
  height: 3rem; /* 48px, down from 56px */
}

The 300ms ease transition makes this feel like the header is settling into a more compact state rather than snapping.

Color flip

When the header uses colorScheme="invert" — designed to sit over a dark hero image — all text, icons, and the CTA button start in their inverted (light) colors and transition to their standard colors once the user scrolls past the threshold. The transition duration for all color changes is 300ms.

To adjust the scroll threshold, change the SCROLL_THRESHOLD constant in Header.astro.

Scroll-triggered Lighthouse scores

The LighthouseScores landing component in src/components/landing/LighthouseScores.astro uses an IntersectionObserver with a threshold: 0.3 to start its animations when the section is 30% visible. Until then, all animations are paused via .animation-paused; the observer switches them to .animation-running on entry and then disconnects.

Each score card fades in and slides up 10px:

@keyframes score-enter {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

Cards animate in sequence: the first starts at 200ms, each subsequent card adds 80ms (0.2s + index × 80ms).

Each score also has an SVG arc that fills from empty to its value. The arc is drawn as a circle with stroke-dasharray: 339.292 (the full circumference at r="54"), and the stroke-dashoffset animates from the full circumference down to zero:

@keyframes progress-fill {
  from { stroke-dashoffset: 339.292; }
  to   { stroke-dashoffset: 0; }
}

This runs for 1 second with ease-out, starting 300ms after the card starts entering — so the number and the arc arrive together.

Back-to-top button with progress ring

BaseLayout.astro includes a back-to-top button that appears after the user scrolls 400px. It animates in and out with independent easings:

  • Show: opacity 0→1 and translate 0 2rem0 0, both 250ms with cubic-bezier(0, 0, 0.2, 1)
  • Hide: opacity 1→0 and translate 0 00 2rem, both 300ms with cubic-bezier(0.4, 0, 1, 1) (faster acceleration out)

The button also carries a circular SVG progress ring. As the user scrolls, the ring’s stroke-dashoffset updates on each animation frame to reflect the scroll percentage through the page:

ring.style.strokeDashoffset = String(CIRCUMFERENCE * (1 - pct));
// CIRCUMFERENCE = 131.95 (2π × r at r="21")

Card hover effects

Every interactive card in the site lifts slightly when hovered. This is a Tailwind utility applied directly in the markup:

<div class="transition-all duration-200 hover:-translate-y-1 hover:shadow-md">

The card moves 4px upward and gains a subtle shadow over 200ms. It creates a tactile feeling that helps users understand which cards are clickable.

UI micro-animations

The full animation library lives in src/styles/global.css. These classes are used throughout the component library and are available for use in your own components.

Transition tokens

All components share a set of CSS custom properties for consistent durations and easings:

--transition-fast:   150ms;
--transition-normal: 200ms;
--transition-slow:   300ms;

--ease-default: cubic-bezier(0.4, 0, 0.2, 1);
--ease-out:     cubic-bezier(0, 0, 0.2, 1);
--ease-spring:  cubic-bezier(0.34, 1.56, 0.64, 1);

Entrance animations

.animate-fade-in    /* fades from transparent to visible — 0.5s ease-out */
.animate-slide-up   /* slides up from 10px below while fading in — 0.5s ease-out */
.animate-slide-down /* slides down from 10px above while fading in — 0.5s ease-out */

Overlay and menu animations

.animate-sheet-up      /* bottom sheet slides up from off-screen — 0.25s */
.animate-sheet-down    /* bottom sheet exits downward — 0.2s */
.animate-menu-down     /* mobile nav drawer opens downward — 0.25s */
.animate-menu-up       /* mobile nav drawer closes upward — 0.2s */
.animate-backdrop      /* backdrop fades in — 0.2s ease-out */
.animate-backdrop-out  /* backdrop fades out — 0.2s ease-out */

These are used by the Dialog, mobile menu, and overlay components. The open motions use cubic-bezier(0.32, 0.72, 0, 1), which produces a fast initial movement that decelerates sharply — a natural, physical feel without overshoot.

.animate-dropdown-in   /* slides down 8px and scales from 0.96 — 0.2s */
.animate-dropdown-out  /* collapses upward and scales back to 0.96 — 0.15s */

The dropdown originates from its trigger point and expands outward, which keeps the motion spatially coherent with the element that opened it.

Feedback animations

.animate-tab-enter   /* crossfades tab panel content — 200ms ease-out */
.animate-toast-in    /* slides toast in from the edge — 350ms ease-spring */
.animate-tooltip-in  /* fades and scales tooltip into view — 150ms ease-out */
.animate-shake       /* brief shake for error feedback — 400ms */

The toast uses --ease-spring (cubic-bezier(0.34, 1.56, 0.64, 1)) for a satisfying overshoot on entry. The shake animation oscillates ±4px horizontally — apply it to a form input on failed validation.

Loading states

.animate-pulse  /* breathing opacity pulse for skeleton loaders — 2s infinite */
.animate-spin   /* continuous rotation for loading spinners — 1s linear */

Stagger utilities

.delay-0  /*   0ms */
.delay-1  /*  50ms */
.delay-2  /* 100ms */
.delay-3  /* 150ms */
.delay-4  /* 200ms */
.delay-5  /* 250ms */

Combine with any entrance animation to stagger multiple elements:

<div class="animate-slide-up delay-0">First item</div>
<div class="animate-slide-up delay-1">Second item</div>
<div class="animate-slide-up delay-2">Third item</div>

Adding scroll-triggered reveals to your own content

The built-in data-reveal system in BaseLayout.astro handles scroll-triggered reveals for you. Add data-reveal to any element and it will fade in when it enters the viewport — no JavaScript required on your end:

<section data-reveal>
  This section fades in when scrolled into view.
</section>

<p data-reveal data-reveal-delay="1">This paragraph is delayed by 100ms.</p>

For immediate reveals on page load (elements already in the viewport), they appear instantly without the fade transition.

Disabling animations

To disable all micro-animations globally while keeping page transitions, remove or comment out the animation class definitions in src/styles/global.css. The @media (prefers-reduced-motion: reduce) block already handles this for users with that system preference set.

To disable page transitions, remove <ClientRouter /> from src/layouts/BaseLayout.astro.

To disable individual component animations, remove the animation class from the component’s markup, or set transition: none on the element.

Back to Blog
Share:

Related Posts

The Hero Typing Effect in Astro Rocket — How It Works and How to Tune It

Astro Rocket's hero headline cycles through words with a typing animation. Learn how it works, how to tune every speed and pause, and how to disable it entirely.

H Hans Martens
2 min read
astro-rocket components customization tutorial javascript

Astro Rocket Configuration — Every Toggle, Theme, and Layout Option Explained

A complete walkthrough of Astro Rocket's configuration options: 12 colour themes, OKLCH colours, typography, radius and shadow tokens, header styles, dark mode, and more.

H Hans Martens
2 min read
astro-rocket configuration customization themes

57 Components Ready to Use — Astro Rocket's Full UI Library

Astro Rocket ships with 57 production-ready components from the Velocity library — buttons, cards, dialogs, forms, data display, and full page-structure components. All accessible, all themed.

H Hans Martens
2 min read
astro-rocket components ui velocity

Follow along

Stay in the loop — new articles, thoughts, and updates.