Group motion components that should perform layout animations together.
By default, motion components with a layout prop will attempt to detect and animate layout changes every time they commit a React render.
It might be the case that components in different trees affect each other's layout.
function ToggleContent({ header, content }) {
const [isOpen, setIsOpen] = useState(false)
return (
<motion.div
layout
onClick={() => setIsOpen(!isOpen)}
>
<motion.h2 layout>{header}</motion.h2>
{isOpen ? content : null}
</motion.div>
)
}
function App() {
return (
<>
<ToggleContent />
<ToggleContent />
</>
)
}function ToggleContent({ header, content }) {
const [isOpen, setIsOpen] = useState(false)
return (
<motion.div
layout
onClick={() => setIsOpen(!isOpen)}
>
<motion.h2 layout>{header}</motion.h2>
{isOpen ? content : null}
</motion.div>
)
}
function App() {
return (
<>
<ToggleContent />
<ToggleContent />
</>
)
}function ToggleContent({ header, content }) {
const [isOpen, setIsOpen] = useState(false)
return (
<motion.div
layout
onClick={() => setIsOpen(!isOpen)}
>
<motion.h2 layout>{header}</motion.h2>
{isOpen ? content : null}
</motion.div>
)
}
function App() {
return (
<>
<ToggleContent />
<ToggleContent />
</>
)
}When the state of one of these ToggleContent components changes, its sibling won't rerender, so it won't perform a layout animation.
This can be fixed by grouping both components with LayoutGroup:
import { LayoutGroup } from "framer-motion"
function App() {
return (
<LayoutGroup>
<ToggleContent />
<ToggleContent />
</LayoutGroup>
)
}import { LayoutGroup } from "framer-motion"
function App() {
return (
<LayoutGroup>
<ToggleContent />
<ToggleContent />
</LayoutGroup>
)
}import { LayoutGroup } from "framer-motion"
function App() {
return (
<LayoutGroup>
<ToggleContent />
<ToggleContent />
</LayoutGroup>
)
}Now, whenever one layout component within LayoutGroup detects and animates layout changes, they all do.
Namespace layoutId
Components expecting to perform shared layout animations are provided a layoutId prop.
function Tab({ label, isSelected }) {
return (
<li>
{label}
{isSelected
? <motion.div layoutId="underline" />
: null}
</li>
)
}
function TabRow({ items }) {
return items.map(item => <Tab {...item} />)
}function Tab({ label, isSelected }) {
return (
<li>
{label}
{isSelected
? <motion.div layoutId="underline" />
: null}
</li>
)
}
function TabRow({ items }) {
return items.map(item => <Tab {...item} />)
}function Tab({ label, isSelected }) {
return (
<li>
{label}
{isSelected
? <motion.div layoutId="underline" />
: null}
</li>
)
}
function TabRow({ items }) {
return items.map(item => <Tab {...item} />)
}layoutId is global, so if multiple instances of TabRow are rendered, only one with layoutId="underline" will render at a time.
To fix this, LayoutGroup can be provided an id prop that namespaces the layoutId for all components within it.
function App() {
return (
<LayoutGroup id="top5">
<TabRow />
</LayoutGroup>
<LayoutGroup id="latest">
<TabRow />
</LayoutGroup>
)
}function App() {
return (
<LayoutGroup id="top5">
<TabRow />
</LayoutGroup>
<LayoutGroup id="latest">
<TabRow />
</LayoutGroup>
)
}function App() {
return (
<LayoutGroup id="top5">
<TabRow />
</LayoutGroup>
<LayoutGroup id="latest">
<TabRow />
</LayoutGroup>
)
}