The types Descriptor: View Transitions' Second Switch

Cross-document view transitions are usually introduced with a single knob: set the @view-transition at-rule's navigation descriptor to auto on the pages you want to animate between, and the browser handles a cross-fade. That works well enough as a default, but it's the only descriptor most documentation mentions. There is a second one, quietly sitting in the spec: the types descriptor, and it exists to solve a problem the simple cross-fade can't touch.

@view-transition {
  navigation: auto;
}

What the Descriptor Actually Declares

The CSS specification's definition is sparse but precise: the types descriptor "sets the active types for the transition when capturing or performing the transition." More concretely, its grammar accepts a space-separated list of <custom-ident> values, or the keyword none when no valid active types exist for the page. Its full descriptor table reads:

  • Name: types
  • For: @view-transition
  • Value: none | <custom-ident>+
  • Initial: none

Which makes declarations like these valid:

@view-transition {
  navigation: auto;
  types: bounce;
}

The operative word here is "active." A page can define several view transitions in its CSS, but they need a mechanism to know which one to run for any given navigation. That's the role active types play: they are the filter that tells the browser which transition gets used and which elements participate in it.

Routing Transitions by Type

Consider a site with paginated content. You'd ideally slide content one direction when the user moves to the next page and the opposite direction when moving back. A single cross-fade can't express that. Without active types, defining multiple ::view-transition animations in your stylesheet would just collide. The intended approach is to declare distinct types and gate each animation behind the :active-view-transition-type() pseudo-class, which matches a transition element only when its specific active type is present.

If your CSS defines a bounce animation like this:

::view-transition-group(root) {
  animation-duration: 0.5s;
}

html:active-view-transition-type(bounce) {
  &::view-transition-group(root) {
    animation-name: bounce-in;
  }
}

That animation only fires on a page whose types descriptor includes bounce. Other view transitions that don't match the active type are simply skipped:

html:active-view-transition-type(slide) {
  &::view-transition-group(root) {
    animation-name: slide-in;
  }
}

There is a critical directional quirk here: the descriptor only governs the transition into the page that declares it. Navigating toward a page with types: bounce triggers the bounce animation; leaving that same page does not. The effect is scoped to the destination page, which is what enables different incoming animations for different routes.

In practice, you can share one stylesheet across pages while each page declares its own type. One page sets types: bounce; the other sets types: slide. Each respective animation only runs when that page is the navigation target.

Filling Active Types from JavaScript

The CSS-only approach has an inherent limit: it can only differentiate transitions based on the page being navigated to, not where the user came from. Pagination and social media profile flows need to know the origin of the navigation, which requires JavaScript.

Per the specification, active types have three distinct sources:

  1. Passed as arguments to startViewTransition(callbackOptions)
  2. Mutated at any point during a transition via the transition's own types property
  3. Declared statically for a cross-document transition via the types descriptor

The descriptor covers the third case. The first option only helps for programmatically started transitions, not for the user clicking a link. The second option is where the real flexibility comes from: you can mutate the active types on the fly right before the transition fires, typically inside a pagereveal event handler. With access to both the current and destination pages in JavaScript, your code can compute which type fits that navigation pair and set it, unlocking directional transitions that the descriptor alone can't express.