In this post, I'm going to share with you a component that I use all the time, both at work and in my personal projects. It's an easy way to level up your UI, specifically by making conditionally rendering UI a breeze. Let's get into it.
I call this component Smooth Mount.
Phase 1
Later in this post, I'll show you what
SmoothMount
has turned into, but it began as a simple combination of AnimatePresence
and motion.div
from the popular animation library Motion.import { AnimatePresence, motion } from "motion/react";
interface ISmoothMountProps {
show: boolean;
}
const SmoothMount = ({ show }: ISmoothMountProps) => {
return (
<AnimatePresence>
{show && (
<motion.div
initial={{ height: 0 }}
animate={{ height: "auto" }}
exit={{ height: 0 }}
>
{children}
</motion.div>
)}
</AnimatePresence>
);
};
This simply animates the height of some conditional UI as the elements are added/removed from the React tree. Pair this with a nice transition and you've got yourself a simple yet elegant component.
Phase 2
Shortly after creating this component, I started working on a component library at work. I wanted to include
SmoothMount
as a component, but felt it was missing something. What if I wanted to animate more than the height
property? Maybe I wanted a nice fade in/out animation when the component was animating.I decided to add a couple more properties to the
SmoothMount
component: fade
and scale
:import { AnimatePresence, motion } from "motion/react";
interface ISmoothMountProps {
show: boolean;
height?: boolean;
fade?: boolean;
scale?: boolean;
}
const SmoothMount = ({
show,
height = true,
fade,
scale,
}: ISmoothMountProps) => {
return (
<AnimatePresence>
{show && (
<motion.div
initial={{
height: height ? 0 : "auto",
opacity: fade ? 0 : 1,
scale: scale ? 0 : 1,
}}
animate={{
height: "auto",
opacity: 1,
scale: 1,
}}
exit={{
height: height ? 0 : "auto",
opacity: fade ? 0 : 1,
scale: scale ? 0 : 1,
}}
>
{children}
</motion.div>
)}
</AnimatePresence>
);
};
This gave me a bit more control over how I wanted the animations to look on a per-implementation basis.
Phase 3
Finally, I wanted to make
SmoothMount
as flexible as possible, so I updated it to:- Extend the
motion.div
type, enabling the spread of additional props. - Accept props for
AnimatePresence
, such asmode
,initial
, andonExitComplete
. Here's what that looks like:
import { AnimatePresence, motion, AnimatePresenceProps } from "motion/react";
import { ComponentProps } from "react";
interface ISmoothMountProps
extends Omit<ComponentProps<typeof motion.div>, "height"> {
show: boolean;
height?: boolean;
fade?: boolean;
scale?: boolean;
mode?: AnimatePresenceProps["mode"];
initialAnimation?: AnimatePresenceProps["initial"];
onExitComplete?: AnimatePresenceProps["onExitComplete"];
}
const SmoothMount = ({
show,
height = true,
fade,
scale,
initial,
animate,
exit,
children,
// AnimatePresence props
mode = "popLayout",
initialAnimation,
onExitComplete,
...props
}: ISmoothMountProps) => {
return (
<AnimatePresence
mode={mode}
initial={initialAnimation}
onExitComplete={onExitComplete}
>
{show && (
<motion.div
initial={{
height: height ? 0 : "auto",
opacity: fade ? 0 : 1,
scale: scale ? 0 : 1,
...(initial as {}),
}}
animate={{
height: "auto",
opacity: 1,
scale: 1,
...(animate as {}),
}}
exit={{
height: height ? 0 : "auto",
opacity: fade ? 0 : 1,
scale: scale ? 0 : 1,
...(exit as {}),
}}
{...props}
>
{children}
</motion.div>
)}
</AnimatePresence>
);
};
export default SmoothMount;
That's it! I created this StackBlitz project as a playground for
SmoothMount
with some examples.⚠️ Don't apply padding or margin to SmoothMount
When Motion calculates the height and width it needs to animate on an element, it doesn't take into account the padding of the element. This causes jumpy animations. If you need padding, apply it to a div within
SmoothMount
.❌ Incorrect
<SmoothMount
...
className="p-8"
>
Howdy!
</SmoothMount>
✅ Correct
<SmoothMount
...
>
<div className="p-8">
Howdy!
</div>
</SmoothMount>
Conclusion
I hope you find
SmoothMount
helpful! It’s a work in progress, and I’ll likely keep enhancing it as I discover new use cases. Feel free to revisit for updates. Until next time, enjoy a cookie! 🫱🍪