Back to blog

View Transitions in Astro with CSS

4 min read

The View Transitions API has been on my radar for a while. I finally tried it on my Astro site with Tailwind v4, and I wanted a hard constraint.

No router. No extra JavaScript.

Turns out you can get smooth page-to-page transitions with only CSS.

Why I Tried This

Astro already feels fast. Pages stream as static HTML and navigation is snappy.

But “fast” and “connected” are different feelings.

I wanted a subtle fade that makes navigation feel continuous without looking like an animation demo.

Cross-document view transitions make that possible. The browser can animate between full page loads. If I could enable it globally, the whole site would pick up that polish without hydration.

It worked better than I expected.

The Core Idea

When you navigate between same-origin pages, the browser can animate the visual change automatically.

You start with one rule:

@view-transition {
navigation: auto;
}

That enables transitions across the site.

After that, you decide what participates by naming elements with view-transition-name.

Two layers make this work:

Implementation

There are two moving parts.

CSS defines what the transition looks like. Markup defines what the browser should “carry over” between pages.

CSS

This is the full setup I used:

@view-transition {
navigation: auto;
}
@theme {
--view-transition: 180ms;
--view-transition-title: 150ms;
--ease: cubic-bezier(0.2, 0.8, 0.2, 1);
}
::view-transition-group(root) {
animation-duration: var(--view-transition);
animation-timing-function: ease-out;
}
::view-transition-group(main) {
animation-duration: var(--view-transition);
animation-timing-function: var(--ease);
}
::view-transition-group(title) {
animation-duration: var(--view-transition-title);
animation-timing-function: var(--ease);
animation-delay: 25ms;
}
::view-transition-old(title),
::view-transition-new(title) {
will-change: transform, opacity;
}
@media (prefers-reduced-motion: reduce) {
@view-transition {
navigation: none;
}
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}

What each part does:

The result is subtle. The page fades. The title shifts first. The rest follows.

Layout

CSS alone is not enough. You need consistent names across pages.

That is what view-transition-name gives you.

<body>
<main class="[view-transition-name:main]">
<h1 class="[view-transition-name:title]">Page Title</h1>
<slot />
</main>
</body>

Rules I follow:

When the browser sees the same name on the old and new document, it animates between them instead of snapping.

What I Learned

A few practical notes after testing:

A note on nav

I leave nav unnamed.

It fades naturally with the rest of the page, which looks intentional and keeps markup simple.

If you want nav to feel “pinned” during transitions, disable its group:

::view-transition-group(nav) {
animation: none;
}

Debugging

If nothing animates, do the boring checks first.

  1. Slow it down

    • If you cannot see it, you cannot debug it.
    ::view-transition-group(root) {
    animation-duration: 600ms;
    }
  2. Use DevTools

    • In Chrome, open the Animations panel.
    • Command+Shift+P then “Show Animations”.
  3. Confirm the basics

    • CSS loads globally
    • Navigation is same-origin
    • Names exist on both pages and match exactly

Timing and Rhythm

Small timing changes matter.

Lead with the title

::view-transition-group(title) {
animation-duration: 150ms;
animation-delay: 0ms;
}
::view-transition-group(main) {
animation-duration: 180ms;
animation-delay: 25ms;
}

The title finishes first. Your eye tracks it. Then content settles.

Move together

::view-transition-group(title),
::view-transition-group(main) {
animation-duration: 180ms;
animation-delay: 0ms;
}

Everything stays synchronized. It feels simpler, but flatter.

Those 25ms gaps decide whether motion feels designed or accidental.

Final Thoughts

This is one of those features that feels like it should require a framework.

It does not.

Astro gives you fast static navigation. The View Transitions API adds continuity. CSS handles the whole thing.

No router. No scripts. Just better page-to-page feel.

References

Let's Discuss

Questions or feedback? Send me an email.

Last updated on

Back to blog