[ @sdxc/ui ]
SharedElement
A plain host that carries a stable view-transition identity across separate page loads.
Pick a cover.
View code
let openSlug: string | null = null;
/** The same call viewTransition() makes for a Frame reload. */
function open(slug: string | null) {
openSlug = slug;
if (typeof document.startViewTransition !== "function") {
void handle.update();
return;
}
document.startViewTransition(() => handle.update());
}
openCover === undefined ? (
<div mix={[vstack({ gap: 3, align: "stretch" })]}>
<Text>Pick a cover.</Text>
<div mix={[grid(), gridTemplate({ columns: "repeat(3, 1fr)" }), gap(3)]}>
{covers.map((cover) => (
<Button
key={cover.slug}
variant="ghost"
mix={[p(0), bs("5rem"), on<HTMLButtonElement, "click">("click", () => open(cover.slug))]}
>
<SharedElement
id={`preview-cover-${cover.slug}`}
mix={[
is("100%"),
bs("100%"),
rounded("lg"),
bg(`${cover.tint}.tint`),
fg(cover.tint),
p(2),
text("xs"),
weight("medium"),
css({ display: "grid", placeItems: "center", textAlign: "center" }),
]}
>
{cover.title}
</SharedElement>
</Button>
))}
</div>
</div>
) : (
<div mix={[vstack({ gap: 3, align: "stretch" })]}>
<SharedElement
id={`preview-cover-${openCover.slug}`}
mix={[
is("100%"),
bs("9rem"),
rounded("lg"),
bg(`${openCover.tint}.tint`),
fg(openCover.tint),
p(4),
text("xl"),
weight("semibold"),
css({ display: "grid", placeItems: "center", textAlign: "center" }),
]}
>
{openCover.title}
</SharedElement>
<Text>The same id on both views, so the browser morphs it instead of swapping it.</Text>
<div mix={[hstack({ gap: 2, align: "center" })]}>
<Button
variant="outline"
size="sm"
mix={[on<HTMLButtonElement, "click">("click", () => open(null))]}
>
Back to the grid
</Button>
</div>
</div>
)Installation
An ordinary dependency: install it, import the theme once, and import the component where it is used.
npm add @sdxc/ui remiximport "@sdxc/ui/theme.css";Usage
import { SharedElement } from "@sdxc/ui";Renders a plain <div> host whose view-transition-name comes from its
own id, so a same-origin, cross-document navigation matching that id
on the other side morphs the element instead of replacing it outright.
Examples
On the book's own page
Built on Standards, by Ana Torres
View code
let onBookPage = true;
function go(toBookPage: boolean) {
onBookPage = toBookPage;
if (typeof document.startViewTransition !== "function") return void handle.update();
document.startViewTransition(() => handle.update());
}
onBookPage ? (
<div mix={[vstack({ gap: 3, align: "stretch" }), is("22rem")]}>
<SharedElement
id={`cover-${book.slug}`}
mix={[
is("8rem"),
bs("11rem"),
rounded("lg"),
bg("brand.tint"),
fg("brand"),
p(3),
weight("semibold"),
css({ display: "grid", placeItems: "center", textAlign: "center" }),
]}
>
{book.title}
</SharedElement>
<Text>{book.title}, by {book.author}</Text>
<div mix={[hstack({ gap: 2, align: "center" })]}>
<Button variant="outline" size="sm" mix={[on<HTMLButtonElement, "click">("click", () => go(false))]}>
Back to the shelf
</Button>
</div>
</div>
) : (
<div mix={[vstack({ gap: 3, align: "stretch" }), is("22rem")]}>
<Button
variant="ghost"
mix={[hstack({ gap: 3, align: "center" }), on<HTMLButtonElement, "click">("click", () => go(true))]}
>
<SharedElement
id={`cover-${book.slug}`}
mix={[
is("2rem"),
bs("2.75rem"),
rounded("sm"),
bg("brand.tint"),
fg("brand"),
text("xs"),
css({ display: "grid", placeItems: "center" }),
]}
>
B
</SharedElement>
{book.title}
</Button>
</div>
)Props
Read from the component's own types, so every prop, every default and every allowed value is listed.
Also accepts everything in TagProps<"div">.