iOS Notifications stack
A iOS inspired notifications stack using Motion for React's variants.
Tutorial
Introduction
The iOS Notifications Stack example shows how to create an expandable stack of notification cards with smooth spring animations. When collapsed, the notifications are stacked on top of each other with a subtle scaling effect. When expanded, they spread out for better visibility, with a header appearing at the top.
In this tutorial, we'll learn to use several Motion for React APIs to create this effect:
- Variants to define animation states
- Motion components to animate DOM elements
Get started
Let's begin by setting up the basic structure and some constants for our notifications stack:
"use client"
import { CSSProperties, useState } from "react"
const N_NOTIFICATIONS = 3
const NOTIFICATION_HEIGHT = 60
const NOTIFICATION_WIDTH = 280
const NOTIFICATION_GAP = 8
export default function NotificationsStack() {
const [isOpen, setIsOpen] = useState(false)
return (
<div style={stackStyle}>
<Header isOpen={isOpen} onClose={() => setIsOpen(false)} />
{Array.from({ length: N_NOTIFICATIONS }).map((_, i) => (
<Notification
key={i}
index={i}
onClick={() => {
setIsOpen((open) => !open)
}}
/>
))}
</div>
)
}
const Header = ({
isOpen,
onClose,
}: {
isOpen: boolean
onClose: () => void
}) => {
return (
<div style={headerStyle}>
<h2 style={headerTitleStyle}>Notifications</h2>
<button style={headerCloseStyle} onClick={onClose}>
Collapse
</button>
</div>
)
}
const Notification = ({
index,
onClick,
}: {
index: number
onClick: () => void
}) => {
const notificationStyle = getNotificationStyle(index)
return <div style={notificationStyle} onClick={onClick} />
}
/**
* ============== Styles ================
*/
const stackStyle: CSSProperties = {
position: "relative",
display: "flex",
flexDirection: "column",
gap: NOTIFICATION_GAP,
}
const headerStyle: CSSProperties = {
position: "absolute",
top: -40,
left: 0,
height: 28,
width: "100%",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
transformOrigin: "bottom center",
pointerEvents: "none",
}
const headerTitleStyle: CSSProperties = {
fontSize: 18,
lineHeight: 1,
marginLeft: 8,
}
const headerCloseStyle: CSSProperties = {
fontSize: 14,
lineHeight: 1,
marginRight: 8,
backgroundColor: "#1f1f1f",
padding: "4px 12px",
height: 20,
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: 16,
cursor: "pointer",
pointerEvents: "auto",
userSelect: "none",
}
/**
* ============== Utils ================
*/
function getNotificationStyle(index: number): CSSProperties {
return {
height: NOTIFICATION_HEIGHT,
width: NOTIFICATION_WIDTH,
backgroundColor: `#ffffff`,
borderRadius: 16,
display: "flex",
justifyContent: "center",
alignItems: "center",
zIndex: N_NOTIFICATIONS - index,
userSelect: "none",
}
}
This code creates the basic structure of our component:
- A parent component
NotificationsStackthat manages the open/closed state - A
Headercomponent that appears when the stack is expanded - Multiple
Notificationcomponents that will be animated
Related examples
Latest in React
Motion+
Unlock all 400+ examples
- Source code for every Plus example.
- Provide examples direct to your agent via Motion's MCP.
- Lifetime access to new examples and APIs.








