Getting Started
Introduction
@codeleap/portals is a modern, powerful solution for creating and managing React portals (modals, drawers, bottom sheets, alerts) with an elegant, fluent API. It provides a declarative approach to portal management, allowing you to create everything from simple alerts to complex form dialogs with built-in request/response patterns.
Key Features
- Fluent API Design - Chain methods for clean, readable portal definitions
- Request/Response System - Portals can return data using async/await patterns
- TypeScript Native - Full type safety for parameters and return values
- Automatic Stack Management - Multiple portals work seamlessly together with z-index control
- React Hooks Integration - Control portal state with familiar React patterns
- Multiple Portal Types - Modal, Drawer, BottomSheet, and Alert utilities
- Global Configuration - Customize wrapper components and default behaviors
- Async Parameters - Load data before displaying portals
- Independent Rendering - Flexible rendering options for advanced use cases
Portals vs Modals — Which to use
@codeleap/portals is the evolution of @codeleap/modals. Use portals for all new code.
@codeleap/portals | @codeleap/modals | |
|---|---|---|
| Modal (centered overlay) | ✅ | ✅ |
| Drawer (side panel) | ✅ | ❌ |
| BottomSheet | ✅ | ❌ |
| Alert utility | ✅ | ❌ |
onOpen / onClose hooks | ✅ | ❌ |
| Portal registry | ✅ | ❌ |
| Use for new code | ✅ | ❌ |
The API is identical for modals — modal().content().props(), .open(), .close(), .request() — so migrating existing modals is straightforward.
Installing the Library
Install @codeleap/portals using your package manager:
bun add @codeleap/portals
# or
npm install @codeleap/portals
# or
yarn add @codeleap/portals
Quick Setup
1. Configure the Global Wrappers (once)
Import and configure at the root of your application:
import { Modal, Drawer, BottomSheet } from '@codeleap/portals'
import { MyModalWrapper } from './components/MyModalWrapper'
import { MyDrawerWrapper } from './components/MyDrawerWrapper'
import { MyBottomSheetWrapper } from './components/MyBottomSheetWrapper'
// Set your custom wrapper components
Modal.WrapperComponent = MyModalWrapper
Drawer.WrapperComponent = MyDrawerWrapper
BottomSheet.WrapperComponent = MyBottomSheetWrapper
// Optional: Configure default transition duration (in milliseconds)
Modal.DEFAULT_TRANSITION_DURATION = 300
Drawer.DEFAULT_TRANSITION_DURATION = 250
BottomSheet.DEFAULT_TRANSITION_DURATION = 200
If your app uses alerts, configure the alert modal in the same static setup layer:
import { Alert, modal } from '@codeleap/portals'
Alert.modal = modal({ id: 'ALERT_MODAL' })
.content((props) => <AlertContent {...props} />)
2. Add the Global Outlet
Add the portal outlet to your main App component:
import { Portal } from '@codeleap/portals'
function App() {
return (
<div>
{/* Your application content */}
<Routes>
{/* Your routes */}
</Routes>
{/* Add this at the end - renders all non-independent portals */}
<Portal.GlobalOutlet />
</div>
)
}
Portal.GlobalOutlet is required for normal non-independent portals, but it is not enough by itself.
For a portal to render correctly, the app still needs:
- the relevant
WrapperComponentassignment Alert.modalsetup when alert helpers are used
If the outlet is mounted but the wrapper setup is missing, the portal may technically open while rendering nothing useful.
3. Create Your First Modal
The portal instance content will always be rendered inside the configured WrapperComponent:
import { modal } from '@codeleap/portals'
const HelloModal = modal()
.content(() => (
<div>
<h2>Hello World!</h2>
<p>Your first modal is working!</p>
</div>
))
// Open it anywhere in your app
HelloModal.open()
4. Create Other Portal Types
You can create different types of portals using the appropriate factory functions:
import { drawer, bottomSheet, modal } from '@codeleap/portals'
// Drawer (side panel)
const SideDrawer = drawer()
.content(() => <div>Drawer content</div>)
// Bottom Sheet
const SettingsSheet = bottomSheet()
.content(() => <div>Bottom sheet content</div>)
// Modal (centered overlay)
const AlertModal = modal()
.content(() => <div>Modal content</div>)
Choosing the Right Portal Type
When the request says "modal" generically, choose the actual UI pattern first:
- use
modal()for centered dialogs and richer interactive content - use
drawer()for side panels, especially on web - use
bottomSheet()for bottom-sliding mobile interactions - use
alert.*(...)for simple warning/info/ask/error prompts
A helpful rule:
- simple prompt content ->
alert - complex content with forms, menus, or multiple controls ->
modal