RSS Amplifier

Karl Koch · Jul 17, 2026

On shared context

0
Sign in to vote or save

Karl Koch · Karl Koch

Most page transitions interrupt the reader. You click a card, the old page disappears and the new page appears, perhaps with a fade or content sliding in. The navigation worked, but your eye has to find the title and image again, then rebuild the relationship between the thing you clicked and the thing you are now reading.

Shared-element transitions preserve object identity across that break. The thumbnail you clicked becomes the hero, the selected card becomes the detail panel, or the active item becomes the heading. The interface still changes state, but one object survives, reducing the effort of navigation.

The disconnected version

The baseline route change is simple:

<a class="card" href="/writing/on-velocity-gating">
  <img src="/images/velocity.jpg" alt="" />
  <h2>On velocity gating</h2>
</a>

The destination has the same content, but the browser does not know that:

<article>
  <img class="hero" src="/images/velocity.jpg" alt="" />
  <h1>On velocity gating</h1>
  <p>In the vinyl shelf post...</p>
</article>

To us, the card image and the hero image are the same object in two contexts. To the browser, they are unrelated elements on unrelated pages, so one disappears before the other appears. The transition feels like a cut.

Naming the survivor

View Transitions give the browser a way to match those elements:

.writing-card[data-slug="on-velocity-gating"] img {
  view-transition-name: writing-image-on-velocity-gating;
}
.writing-article[data-slug="on-velocity-gating"] .hero {
  view-transition-name: writing-image-on-velocity-gating;
}

The browser can now capture the old and new geometry, then animate between them. The card image moves into the article hero, so the reader keeps track of it.

For cross-document navigation, the page opts in:

@view-transition {
  navigation: auto;
}

The name must be unique on a page and stable across both states. A shared transition works when that name maps to one object.

Continuity switch

Here is the same state change in three modes: instant, generic fade, and shared element. Slow it down to 0.25x and compare what your eye has to do after each click.

The useful moment is comparing mode two and mode three. A fade makes change softer. A shared transition makes change legible.

Duplicate names break the spell

The easiest mistake is naming by component instead of instance:

.writing-card img {
  view-transition-name: writing-image;
}

That fails as soon as the page contains more than one writing card. Multiple elements claim the same transition name, so the browser cannot know which should become the hero.

Give each object its own name:

<a
  className="writing-card"
  href={`/writing/${post.slug}`}
  style={{ "--transition-name": `writing-image-${post.slug}` }}
>
  <img
    src={post.hero}
    alt=""
    style={{ viewTransitionName: `writing-image-${post.slug}` }}
  />
</a>

The transition name belongs to the object, not the component class. This is the same mental model as React keys. If identity is vague, the UI will eventually do something strange.

Motion should explain the state change

The first working shared transition can tempt you to animate every surface. If everything morphs, the motion explains nothing.

The filter I use is: did this exact object survive?

A product card image becoming a product hero passes. A selected album cover expanding into a now-playing view passes. An avatar in a list becoming the profile header passes. A random decorative blob becoming another decorative blob does not. A button turning into a modal because the shapes line up probably does not.

The motion should answer a question the user already has: “where did the thing I clicked go?”

The root transition is not enough

The default View Transition captures the old page and new page as snapshots. That alone can give you a crossfade:

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 180ms;
}

This is fine for softening a navigation. It is not the same as preserving context.

The root transition says “the page changed.” A named shared element says “this object moved.” Those are different design jobs.

For writing pages, I would keep the root transition almost invisible and let one or two named elements carry the meaning:

::view-transition-group(root) {
  animation-duration: 120ms;
}
::view-transition-group(writing-image-on-velocity-gating) {
  animation-duration: 360ms;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
}

Keep the root transition subdued and let the shared object guide the eye.

Reduced motion still matters

Shared context is helpful, but it is still spatial movement. If someone has asked for reduced motion, the interface should respect that.

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0.01ms;
  }
}

The navigation, hierarchy, and destination content still work. Motion is an enhancement rather than the only thing making the flow understandable, and the reduced-motion alternative still needs a complete design.

View Transitions vs Motion

I would reach for View Transitions when the browser is already handling the state boundary: page navigation, simple route changes, or document-to-document continuity.

I would reach for Motion when the interaction needs interruption, gesture control, drag, spring physics, exit sequencing, or many elements animating under direct user input.

The distinction is practical. View Transitions animate snapshots. Motion animates live elements. Snapshots are excellent for route continuity. Live elements are better when the user might grab, reverse, interrupt, or scrub the interaction.

This sits next to velocity gating and clip-path reveals because each primitive serves a different job. Choose one whose constraints suit the component.

When to steal this

Use shared-element transitions when an object clearly persists across a state change:

  • card to detail
  • thumbnail to hero
  • selected item to expanded panel
  • avatar to profile header
  • compact media row to now-playing surface

Avoid them when the relationship is only visual. Similar shape is not the same as shared identity.

The API supplies the geometry. You decide which object deserves continuity.

Read the original on karlkoch.me

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.