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/uiimport "@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 — the matching `id` lets the browser morph
// between the two instead of swapping the image outright.
<SharedElement id={`cover-${book.slug}`}>
<img src={book.coverUrl} alt={book.title} />
</SharedElement>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">.