The area that changes between pages slides and fades into the next, using the browser's view transitions.
'use client';
import { BrandImage, PageTransition, transitionTo } from '@brand-studio/ui';
import { useState } from 'react';
import { cameraViews } from '@/demos/assets';
const pages = [
{ id: 'camera', label: 'Camera', title: 'Halden R', body: 'A rangefinder with three dials and no menus.', image: cameraViews[0] },
{ id: 'lens', label: 'Lens', title: '35 mm f/2', body: 'One fixed lens, sharp from edge to edge.', image: cameraViews[1] },
{ id: 'grip', label: 'Grip', title: 'Walnut grip', body: 'Cut from one piece and oiled by hand.', image: cameraViews[2] },
];
export default function PageTransitionDemo() {
const [page, setPage] = useState(pages[0]);
return (
<div style={{ width: '100%', maxWidth: 640, border: '1px solid var(--bs-line-soft)', borderRadius: 20, overflow: 'hidden', background: 'var(--bs-elevated)' }}>
<nav aria-label="Product" style={{ display: 'flex', gap: 4, padding: 8, borderBottom: '1px solid var(--bs-line-soft)' }}>
{pages.map(item => (
<button key={item.id} type="button" aria-current={item.id === page.id ? 'page' : undefined} onClick={() => transitionTo(() => setPage(item))}
style={{ minHeight: 40, padding: '0 16px', border: 0, borderRadius: 999, font: 'inherit', fontWeight: 600, cursor: 'pointer', background: item.id === page.id ? 'var(--bs-line-soft)' : 'transparent', color: 'inherit' }}>
{item.label}
</button>
))}
</nav>
<PageTransition>
<div key={page.id} style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', alignItems: 'center', gap: 16, padding: 24 }}>
<BrandImage asset={page.image} sizes="300px" className="demo-page-image" />
<div>
<h3 style={{ margin: '0 0 8px', fontSize: 28 }}>{page.title}</h3>
<p style={{ margin: 0, color: 'var(--bs-muted)' }}>{page.body}</p>
</div>
</div>
</PageTransition>
<style>{'.demo-page-image { aspect-ratio: 1; }'}</style>
</div>
);
}Installation
pnpm add @brand-studio/uinpm install @brand-studio/uiyarn add @brand-studio/uibun add @brand-studio/uiInstall the peer dependencies
npm install react react-domImport the stylesheet once
import '@brand-studio/ui/styles.css';Wrap your app in a brand theme
import { BrandTheme, PageTransition } from '@brand-studio/ui'; import brand from './brand.json'; <BrandTheme palette={brand.tokens} mode="system"> {/* your app */} </BrandTheme>
Usage
import { PageTransition, transitionTo } from '@brand-studio/ui';On a multi-page site, wrap the main area of every page. The browser animates each same-site link between pages:
<PageTransition>
<main>{children}</main>
</PageTransition>In an app, run the route or state change through transitionTo:
<button onClick={() => transitionTo(() => setPage('lens'))}>Lens</button>variant is 'slide' or 'fade'. Use one PageTransition per page; give each its own name if you need two.
Accessibility
- It changes nothing about focus or reading order; move focus to the new heading yourself when the page changes.
- Browsers without view transitions, and reduced motion, change pages at once.
API Reference
| Prop | Type | Default |
|---|---|---|
variant? | "fade" | "slide" | 'slide' |
name? | string | 'bs-page' |
children? | React.ReactNode | – |
className? | string | '' |
