Skip to main content

useViewTransitionState

Returns true when there is an active View Transition to the specified location. This can be used to apply finer-grained styles to elements to further customize the view transition.
import { useViewTransitionState } from "react-router";

function ImagePreview({ src }: { src: string }) {
  const isTransitioning = useViewTransitionState("/images/123");

  return (
    <img
      src={src}
      style={{
        viewTransitionName: isTransitioning ? "image-expand" : "",
      }}
    />
  );
}

Parameters

to
To
required
The location to check for an active View Transition. Can be a string path or a partial Path object with pathname, search, and hash.
options
object
relative
"route" | "path"
default:"\"route\""
The relative routing type to use when resolving the to location:
  • "route" (default) - Relative to the route hierarchy
  • "path" - Relative to the URL path segments

Return Value

isTransitioning
boolean
true if there is an active View Transition to the specified location, otherwise false.

Type Declaration

declare function useViewTransitionState(
  to: To,
  options?: { relative?: "route" | "path" }
): boolean;

type To = string | Partial<Path>;

interface Path {
  pathname: string;
  search: string;
  hash: string;
}

Usage Examples

import { useViewTransitionState, Link } from "react-router";

function ImageGallery({ images }: { images: Image[] }) {
  return (
    <div className="gallery">
      {images.map((image) => (
        <ImageThumbnail key={image.id} image={image} />
      ))}
    </div>
  );
}

function ImageThumbnail({ image }: { image: Image }) {
  const to = `/images/${image.id}`;
  const isTransitioning = useViewTransitionState(to);

  return (
    <Link to={to} viewTransition>
      <img
        src={image.thumbnailUrl}
        alt={image.alt}
        style={{
          viewTransitionName: isTransitioning ? `image-${image.id}` : "",
        }}
      />
    </Link>
  );
}

Animated List Items

import { useViewTransitionState, Link } from "react-router";

function ProductList({ products }: { products: Product[] }) {
  return (
    <ul>
      {products.map((product) => (
        <ProductItem key={product.id} product={product} />
      ))}
    </ul>
  );
}

function ProductItem({ product }: { product: Product }) {
  const to = `/products/${product.id}`;
  const isTransitioning = useViewTransitionState(to);

  return (
    <li
      style={{
        viewTransitionName: isTransitioning ? `product-${product.id}` : "",
      }}
    >
      <Link to={to} viewTransition>
        <h3>{product.name}</h3>
        <p>{product.description}</p>
      </Link>
    </li>
  );
}

Conditional Transition Styles

import { useViewTransitionState } from "react-router";

function Card({ id, title }: { id: string; title: string }) {
  const isExpanding = useViewTransitionState(`/cards/${id}`);
  const isCollapsing = useViewTransitionState("/cards");

  return (
    <div
      className="card"
      style={{
        viewTransitionName: isExpanding || isCollapsing ? `card-${id}` : "",
      }}
    >
      <h2>{title}</h2>
    </div>
  );
}

Multiple Transition Elements

import { useViewTransitionState, Link } from "react-router";

function ArticlePreview({ article }: { article: Article }) {
  const to = `/articles/${article.slug}`;
  const isTransitioning = useViewTransitionState(to);

  return (
    <article>
      <Link to={to} viewTransition>
        <img
          src={article.coverImage}
          style={{
            viewTransitionName: isTransitioning
              ? `article-image-${article.slug}`
              : "",
          }}
        />
        <h2
          style={{
            viewTransitionName: isTransitioning
              ? `article-title-${article.slug}`
              : "",
          }}
        >
          {article.title}
        </h2>
      </Link>
    </article>
  );
}

Common Patterns

Shared Element Transitions

import { useViewTransitionState, useParams, Link } from "react-router";

// List view
function MovieList({ movies }: { movies: Movie[] }) {
  return (
    <div>
      {movies.map((movie) => (
        <MovieCard key={movie.id} movie={movie} />
      ))}
    </div>
  );
}

function MovieCard({ movie }: { movie: Movie }) {
  const to = `/movies/${movie.id}`;
  const isTransitioning = useViewTransitionState(to);

  return (
    <Link to={to} viewTransition>
      <img
        src={movie.poster}
        style={{
          viewTransitionName: isTransitioning ? `movie-poster-${movie.id}` : "",
        }}
      />
    </Link>
  );
}

// Detail view
function MovieDetail() {
  const { id } = useParams();
  const isTransitioning = useViewTransitionState("/movies");

  return (
    <div>
      <img
        src={movie.poster}
        style={{
          viewTransitionName: isTransitioning ? `movie-poster-${id}` : "",
        }}
      />
    </div>
  );
}

Dynamic Transition Names

import { useViewTransitionState } from "react-router";

function DynamicTransition({ item, targetPath }: { item: Item; targetPath: string }) {
  const isTransitioning = useViewTransitionState(targetPath);

  const transitionName = isTransitioning
    ? `${item.type}-${item.id}`
    : "";

  return (
    <div
      style={{
        viewTransitionName: transitionName,
      }}
    >
      {item.content}
    </div>
  );
}

Back Navigation Transitions

import { useViewTransitionState, useNavigate } from "react-router";

function DetailView({ id }: { id: string }) {
  const navigate = useNavigate();
  const isReturning = useViewTransitionState("/list");

  return (
    <div
      style={{
        viewTransitionName: isReturning ? `item-${id}` : "",
      }}
    >
      <button onClick={() => navigate("/list", { viewTransition: true })}>
        Back to List
      </button>
      {/* Detail content */}
    </div>
  );
}

CSS Integration

Defining View Transitions

/* Define the transition for the expanding image */
::view-transition-old(image-expand),
::view-transition-new(image-expand) {
  animation-duration: 0.3s;
  animation-timing-function: ease-in-out;
}

/* Customize the transition direction */
::view-transition-old(image-expand) {
  animation-name: shrink;
}

::view-transition-new(image-expand) {
  animation-name: expand;
}

@keyframes expand {
  from {
    transform: scale(0.8);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}

@keyframes shrink {
  from {
    transform: scale(1);
    opacity: 1;
  }
  to {
    transform: scale(0.8);
    opacity: 0;
  }
}

Complex Transitions

import { useViewTransitionState } from "react-router";

function ComplexCard({ id }: { id: string }) {
  const isTransitioning = useViewTransitionState(`/items/${id}`);

  return (
    <div className={isTransitioning ? "transitioning" : ""}>
      <div
        className="card-image"
        style={{
          viewTransitionName: isTransitioning ? `card-image-${id}` : "",
        }}
      />
      <div
        className="card-title"
        style={{
          viewTransitionName: isTransitioning ? `card-title-${id}` : "",
        }}
      />
      <div
        className="card-content"
        style={{
          viewTransitionName: isTransitioning ? `card-content-${id}` : "",
        }}
      />
    </div>
  );
}

Requirements

  1. View transitions must be enabled via the viewTransition prop on navigation:
    <Link to="/path" viewTransition>Link</Link>
    <Form viewTransition>...</Form>
    navigate("/path", { viewTransition: true });
    submit(data, { viewTransition: true });
    
  2. Browser support - View Transitions API is not supported in all browsers
  3. Must be used within RouterProvider from react-router-dom

Browser Support

View Transitions are a modern web API with limited browser support. Check Can I Use for current browser compatibility. For unsupported browsers, the transition simply won’t occur, and navigation will work normally.

Notes

  • Available in Framework and Data modes only
  • Must be used within react-router-dom’s RouterProvider (not the one from react-router)
  • Returns true for both forward and backward transitions to the specified location
  • The viewTransitionName CSS property must be set for transitions to work
  • Each viewTransitionName should be unique within the document
  • Transitions work bidirectionally (going to and coming from the location)

Build docs developers (and LLMs) love