Hero Transition

Transitions maintaining context through shared element continuity

Hero Transition

The hero transition is a continuity transition where shared elements between two screens naturally move and transform. Elements "travel" from one screen to another, providing users with strong visual connectivity and context.

Demo

Loading demo...

UX Principles

When to Use?

Hero transitions are used when continuity and context preservation are important.

Content Relationship Suitability

콘텐츠 관계적합성설명
Unrelated ContentUnsuitable when there are no shared elements
Sibling Relationship⚠️Effective in specific situations like galleries
Hierarchical RelationshipOptimal for different views of the same object (list→detail)

Key Use Cases

  • Gallery → Fullscreen: Image expands to detailed view
  • Product List → Detail: Product card expands to detail page
  • Profile → Edit: Profile photo maintains while transitioning to edit mode
  • Media Player: Mini player expands to full player

Why Does It Work This Way?

  1. Visual Continuity: Track element movement to maintain spatial orientation
  2. Spatial Relationship: From small to large, summary to detail
  3. Minimize Cognitive Load: Natural transformation instead of abrupt changes
  4. Interest and Delight: Dynamic motion increases user engagement

Motion Design

Hero Element Journey:
1. Starting Point → Record position, size, shape
2. During Transform → Smooth interpolation animation
3. Destination → Settle into new position and size

Other Elements:
- Outgoing Page: Fade out
- Incoming Page: Fade in after hero element settles

Basic Usage

1. Transition Setup

import { Ssgoi } from '@ssgoi/react';
import { hero } from '@ssgoi/react/view-transitions';

const config = {
  transitions: [
    {
      from: '/gallery',
      to: '/gallery/*',
      transition: hero(),
      symmetric: true
    }
  ]
};

export default function App() {
  return (
    <Ssgoi config={config}>
      {/* App content */}
    </Ssgoi>
  );
}

2. Marking Elements

Add data-hero-key attribute to shared elements:

// List Page
function ProductList() {
  return (
    <div className="grid">
      {products.map(product => (
        <Link key={product.id} to={`/product/${product.id}`}>
          <img 
            data-hero-key={`product-${product.id}`}
            src={product.image}
            alt={product.name}
          />
          <h3>{product.name}</h3>
        </Link>
      ))}
    </div>
  );
}

// Detail Page
function ProductDetail({ product }) {
  return (
    <div>
      <img 
        data-hero-key={`product-${product.id}`}
        src={product.image}
        alt={product.name}
        className="w-full"
      />
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  );
}

Practical Examples

Thumbnail to fullscreen expansion:

// Gallery Grid
function Gallery() {
  return (
    <div className="grid grid-cols-3 gap-4">
      {images.map(img => (
        <Link to={`/photo/${img.id}`} key={img.id}>
          <div 
            data-hero-key={`photo-${img.id}`}
            className="aspect-square overflow-hidden"
          >
            <img src={img.thumb} className="object-cover w-full h-full" />
          </div>
        </Link>
      ))}
    </div>
  );
}

// Fullscreen View
function PhotoView({ id }) {
  return (
    <div 
      data-hero-key={`photo-${id}`}
      className="fixed inset-0 bg-black"
    >
      <img src={image.full} className="w-full h-full object-contain" />
    </div>
  );
}

2. Card Expansion

Small card to fullscreen detail:

// Card List
function CardGrid() {
  return (
    <div className="grid gap-6">
      {items.map(item => (
        <article 
          key={item.id}
          data-hero-key={`card-${item.id}`}
          onClick={() => navigate(`/item/${item.id}`)}
          className="bg-white rounded-lg shadow-lg p-4 cursor-pointer"
        >
          <h3>{item.title}</h3>
          <p>{item.summary}</p>
        </article>
      ))}
    </div>
  );
}

// Expanded Detail
function ItemDetail({ item }) {
  return (
    <article 
      data-hero-key={`card-${item.id}`}
      className="min-h-screen bg-white p-8"
    >
      <h1 className="text-4xl">{item.title}</h1>
      <p className="text-lg">{item.fullContent}</p>
    </article>
  );
}

3. Multiple Hero Elements

Multiple elements transitioning simultaneously:

// Profile Card
function ProfileCard({ user }) {
  return (
    <div onClick={() => navigate(`/profile/${user.id}`)}>
      <img 
        data-hero-key={`avatar-${user.id}`}
        src={user.avatar}
        className="w-16 h-16 rounded-full"
      />
      <h3 data-hero-key={`name-${user.id}`}>
        {user.name}
      </h3>
    </div>
  );
}

// Profile Detail
function ProfileDetail({ user }) {
  return (
    <div>
      <img 
        data-hero-key={`avatar-${user.id}`}
        src={user.avatar}
        className="w-32 h-32 rounded-full"
      />
      <h1 data-hero-key={`name-${user.id}`}>
        {user.name}
      </h1>
    </div>
  );
}

Important Notes

Hero Key Rules

  • data-hero-key must be unique on each page
  • Keys are strings and can be generated dynamically
  • Only elements with the same key are connected

Performance Considerations

  • Hero elements are cloned in the DOM for animation
  • Too many hero elements impact performance
  • Preload images to prevent flickering

Accessibility

  • Instant transition when motion is reduced
  • Focus management supports keyboard navigation
  • Screen readers perceive as normal page transition

Best Practices

✅ DO

  • Use for visually important elements
  • Apply to different representations of the same object
  • Maintain natural size/position changes
  • Use to guide user attention

❌ DON'T

  • Don't apply to too many elements simultaneously
  • Don't connect unrelated elements
  • Avoid extreme size changes (more than 10x)
  • Use cautiously with text content