Skip to main content

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 WrapperComponent assignment
  • Alert.modal setup 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