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
{
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,
}: {
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 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 →