Join the waitlist
Be among the first to experience our next-generation platform. Get early access to exclusive features and help shape the future of productivity.
References
Installation
pnpm dlx shadcn@latest add https://cult-ui.com/r/expandable-screen.json
Usage
The ExpandableScreen component creates a morphing animation effect where a trigger element smoothly expands into a full-screen overlay. It uses Framer Motion's layoutId feature to create seamless transitions between states.
Basic Example
import {
ExpandableScreen,
ExpandableScreenContent,
ExpandableScreenTrigger,
} from "@/components/ui/expandable-screen"
export default function ExpandableScreenDemo() {
return (
<ExpandableScreen
layoutId="cta-card"
triggerRadius="100px"
contentRadius="24px"
>
<div className="flex min-h-screen items-center justify-center">
<ExpandableScreenTrigger>
<button className="bg-primary px-6 py-3 text-primary-foreground">
Open Screen
</button>
</ExpandableScreenTrigger>
</div>
<ExpandableScreenContent className="bg-primary">
<div className="flex h-full items-center justify-center p-8">
<h2 className="text-4xl text-primary-foreground">
Full Screen Content
</h2>
</div>
</ExpandableScreenContent>
</ExpandableScreen>
)
}Components
ExpandableScreen
The root component that provides context and manages the expanded state. It uses a shared layoutId to create morphing animations between the trigger and content.
<ExpandableScreen
layoutId="unique-id"
triggerRadius="100px"
contentRadius="24px"
animationDuration={0.3}
>
{/* Child components */}
</ExpandableScreen>Props:
layoutId?: string- Unique identifier for the shared layout animation (default: "expandable-card")triggerRadius?: string- Border radius for the trigger element (default: "100px")contentRadius?: string- Border radius for the expanded content (default: "24px")animationDuration?: number- Duration of the morphing animation in seconds (default: 0.3)defaultExpanded?: boolean- Initial expanded state (default: false)onExpandChange?: (expanded: boolean) => void- Callback when expanded state changeslockScroll?: boolean- Whether to lock body scroll when expanded (default: true)
ExpandableScreenTrigger
The trigger element that expands into the full screen. This component automatically hides when expanded and shows when collapsed.
<ExpandableScreenTrigger>
<button>Click to Expand</button>
</ExpandableScreenTrigger>Props:
className?: string- Additional CSS classes
Note: The trigger automatically disappears when the screen is expanded and reappears when collapsed.
ExpandableScreenContent
The full-screen content that appears when expanded. It morphs from the trigger element using the shared layoutId.
<ExpandableScreenContent className="bg-primary">
{/* Full screen content */}
</ExpandableScreenContent>Props:
className?: string- Additional CSS classesshowCloseButton?: boolean- Whether to show the close button (default: true)closeButtonClassName?: string- Additional CSS classes for the close button
ExpandableScreenBackground
An optional component for rendering different content based on the expanded state. Useful for background elements.
<ExpandableScreenBackground
trigger={<div>Trigger Background</div>}
content={<div>Expanded Background</div>}
/>Props:
trigger?: ReactNode- Content to show when collapsedcontent?: ReactNode- Content to show when expandedclassName?: string- Additional CSS classes
useExpandableScreen Hook
Hook to access the expandable screen context and control the expanded state.
function CustomComponent() {
const { isExpanded, expand, collapse, layoutId } = useExpandableScreen()
return (
<div>
<p>Is expanded: {isExpanded ? "Yes" : "No"}</p>
<button onClick={expand}>Expand</button>
<button onClick={collapse}>Collapse</button>
</div>
)
}Returns:
isExpanded: boolean- Whether the screen is currently expandedexpand: () => void- Function to expand the screencollapse: () => void- Function to collapse the screenlayoutId: string- The shared layout ID for morphing animationstriggerRadius: string- Border radius of the triggercontentRadius: string- Border radius of the contentanimationDuration: number- Animation duration
Advanced Usage
Custom Layout IDs
Use unique layoutId values when you have multiple expandable screens on the same page:
<ExpandableScreen layoutId="waitlist-form">
{/* Content */}
</ExpandableScreen>
<ExpandableScreen layoutId="contact-form">
{/* Different content */}
</ExpandableScreen>Controlled State
Control the expanded state externally:
const [isExpanded, setIsExpanded] = useState(false)
<ExpandableScreen
layoutId="controlled-screen"
defaultExpanded={isExpanded}
onExpandChange={setIsExpanded}
>
{/* Content */}
</ExpandableScreen>Custom Close Button
Customize or hide the close button:
<ExpandableScreenContent
showCloseButton={false}
className="bg-primary"
>
<div className="flex h-full items-center justify-center">
<button onClick={() => collapse()}>Custom Close</button>
</div>
</ExpandableScreenContent>Different Border Radius
Create different visual styles by adjusting the border radius:
<ExpandableScreen
layoutId="rounded-card"
triggerRadius="12px"
contentRadius="0px"
>
{/* Content */}
</ExpandableScreen>How It Works
The ExpandableScreen component uses Framer Motion's layoutId feature to create morphing animations. Here's how it works:
-
Shared Layout ID: Both the trigger and content share the same
layoutId, which tells Framer Motion they're the same element in different states. -
Layout Animation: When the state changes, Framer Motion automatically animates the position, size, and shape between the two elements.
-
Smooth Transitions: The component uses
layoutanimations combined with opacity transitions for smooth, performant animations. -
Scroll Locking: By default, the component locks body scroll when expanded to prevent background scrolling.
Customization
The ExpandableScreen component is highly customizable:
- Adjust animation duration for faster or slower transitions
- Customize border radius for different visual styles
- Control scroll locking behavior
- Customize close button appearance or hide it entirely
- Use different layout IDs for multiple instances
Accessibility
The ExpandableScreen component includes:
- Keyboard support (Escape key closes the expanded screen)
- Proper ARIA labels on the close button
- Focus management
- Scroll locking to prevent background interaction when expanded
Performance
The component is optimized for performance:
- Uses
transform-gpuandwill-change-transformfor GPU acceleration - Leverages Framer Motion's layout animations for efficient transitions
- Minimal re-renders with context-based state management