Shared Layout Transition
Shared Layout Transition brings controlled expression to your interface.
Make it yours.
Install the editable source and its dependencies into your React + Tailwind project.
Scroll horizontally for long lines
pnpm dlx shadcn@4.0.8 add http://localhost:3000/r/shared-layout-transition.jsonUse it
Scroll horizontally for long lines
"use client";
import { SharedLayoutTransition } from "@/components/jez-ui/ui/shared-layout-transition";
export default function Example() {
return (
<>
<SharedLayoutTransition
items={[
{
id: "one",
title: "Explore the idea",
description:
"Open a little space for the unexpected. This panel expands while its neighbours move naturally.",
},
{
id: "two",
title: "Find the next step",
description: "A small, clear action is often all you need.",
},
]}
/>
</>
);
}
Props & types
Scroll horizontally for long lines
React.ComponentProps<"div"> & {
items: { id: string; title: string; description: string }[];
className?: string;
}Source
View 2 source files
registry/ui/shared-layout-transition.tsx
Scroll horizontally for long lines
"use client";
import * as React from "react";
import { cn } from "./utils";
import { motion, LayoutGroup, useReducedMotion } from "motion/react";
export function SharedLayoutTransition({
items,
className,
...props
}: React.ComponentProps<"div"> & {
items: { id: string; title: string; description: string }[];
className?: string;
}) {
const [active, setActive] = React.useState<string | null>(null);
const id = React.useId();
const reduce = useReducedMotion();
return (
<LayoutGroup id={id}>
<div {...props} className={cn("grid gap-3", className)}>
{items.map((i) => (
<motion.button
key={i.id}
layout
transition={{ duration: reduce ? 0 : 0.3 }}
onClick={() => setActive(active === i.id ? null : i.id)}
aria-expanded={active === i.id}
className="rounded-xl border border-border p-5 text-left"
>
<motion.span layout="position" className="block font-medium">
{i.title}
</motion.span>
{active === i.id && (
<motion.span
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="mt-3 block text-sm text-muted-foreground"
>
{i.description}
</motion.span>
)}
</motion.button>
))}
</div>
</LayoutGroup>
);
}
registry/ui/utils.ts
Scroll horizontally for long lines
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...values: ClassValue[]) {
return twMerge(clsx(values));
}
Dependencies
motion@13.2.0 · clsx@2.1.1 · tailwind-merge@3.5.0
Accessibility & behaviour
Respect reduced-motion preferences. Decorative effects must not carry essential information. WebGL scenes include a static fallback and only render while visible.
Read the accessibility and performance guide →