Skip to main content

API Reference

Complete reference for all portal methods, properties, and hooks.

Portal Creation

Factory Functions

import { modal, drawer, bottomSheet } from '@codeleap/portals'

// Create a modal
const MyModal = modal()
const MyModalWithId = modal({ id: 'MY_MODAL' })
const ConfiguredModal = modal({
id: 'CONFIGURED',
initialParams: { theme: 'dark' },
startsOpen: false,
independent: false,
rendersWhenHidden: false,
resetParamsOnClose: true,
transitionDuration: 300,
metadata: { category: 'user' }
})

// Create a drawer
const MyDrawer = drawer()

// Create a bottom sheet
const MyBottomSheet = bottomSheet()

Configuration Options

OptionTypeDefaultDescription
idstringauto-generatedUnique identifier for the portal
initialParamsParams | () => Promise<Params>{}Initial parameters or async loader
startsOpenboolean | () => Promise<boolean>falseWhether to open on mount
independentbooleanfalseIf true, doesn't participate in global stack
rendersWhenHiddenbooleanfalseIf true, renders even when not visible
resetParamsOnClosebooleantrueReset params to initial values on close
transitionDurationnumberclass defaultAnimation duration in milliseconds
metadataMetadata{}Additional metadata for the portal

Core Methods

.content(render)

Defines the content render function. Required for the portal to display.

MyModal.content((props) => {
const { visible, toggle, close, open, setParams, request, ...params } = props

return (
<div>
<h2>Modal Content</h2>
<p>Visible: {visible ? 'Yes' : 'No'}</p>
<button onClick={toggle}>Toggle</button>
</div>
)
})

Parameters:

  • render: (props) => JSX.Element - Function that receives props and returns JSX

Returns: The portal instance (for chaining)

.props(props)

Sets wrapper component props. Can be static or dynamic.

// Static props
MyModal.props({
title: 'My Modal',
style: ['modal', 'elevated'],
showClose: true
})

// Dynamic props (based on params)
MyModal.props((params) => ({
title: `Editing ${params.itemName}`,
style: params.isImportant ? ['modal', 'urgent'] : ['modal']
}))

Parameters:

  • props: WrapperProps | (params: Params) => WrapperProps - Props object or function

Returns: The portal instance (for chaining)

.open(params?)

Opens the portal with optional parameters.

// Open without params
MyModal.open()

// Open with params
MyModal.open({
userId: '123',
mode: 'edit'
})

Parameters:

  • params?: Partial<Params> - Parameters to merge with current params

Returns: Promise<void>

.close()

Closes the portal and optionally resets parameters.

MyModal.close()

Returns: Promise<void>

.toggle()

Toggles portal visibility (opens if closed, closes if open).

MyModal.toggle()

Returns: Promise<void>

.request(params?, force?)

Opens the portal and returns a promise that resolves when the portal is closed with a result.

const result = await ConfirmModal.request({
message: 'Are you sure?'
})

if (result === true) {
console.log('User confirmed')
}

// Force new request even if one is pending
const result2 = await ConfirmModal.request({ message: 'Force' }, true)

Parameters:

  • params?: Partial<Params> - Parameters to pass to the portal
  • force?: boolean - Force new request even if one is pending (default: false)

Returns: Promise<Result>

.setParams(next)

Updates portal parameters without opening.

// Object merge
MyModal.setParams({ theme: 'dark' })

// Updater function
MyModal.setParams((prev) => ({
...prev,
count: prev.count + 1
}))

Parameters:

  • next: Partial<Params> | (prev: Params) => Partial<Params> - New params or updater

Returns: void

.resetParams()

Resets parameters to initial values.

MyModal.resetParams()

Returns: void

.getParams()

Gets current parameter values.

const params = MyModal.getParams()
console.log(params)

Returns: Params

.onOpen(callback)

Registers a callback to execute when the portal opens.

MyModal.onOpen((portal) => {
console.log('Modal opened!', portal.id)
console.log('Current params:', portal.getParams())
})

Parameters:

  • callback: (portal) => void - Function to execute on open

Returns: The portal instance (for chaining)

.onClose(callback)

Registers a callback to execute when the portal closes.

MyModal.onClose((portal) => {
console.log('Modal closed!', portal.id)
})

Parameters:

  • callback: (portal) => void - Function to execute on close

Returns: The portal instance (for chaining)

React Hooks

.useState()

React hook to access portal state (visibility and params).

function MyComponent() {
const { visible, params } = UserModal.useState()

return (
<div>
<p>Modal is {visible ? 'open' : 'closed'}</p>
<p>User: {params.username}</p>
<button onClick={UserModal.toggle}>
{visible ? 'Close' : 'Open'}
</button>
</div>
)
}

Returns:

  • visible: boolean - Current visibility state
  • params: Params - Current parameters

.useProps(props, deps?)

React hook to update wrapper props dynamically with dependency tracking.

function MyComponent({ theme, title }) {
const [isLoading, setIsLoading] = useState(false)

MyModal.useProps({
title: isLoading ? 'Loading...' : title,
theme: theme,
disabled: isLoading
}, [theme, title, isLoading])

return <button onClick={MyModal.open}>Open</button>
}

Parameters:

  • props: WrapperProps - Props to merge with existing wrapper props
  • deps?: React.DependencyList - Dependency array (optional, default: [])

Returns: void

Read-Only Properties

.isVisible

Current visibility state.

if (MyModal.isVisible) {
console.log('Modal is open')
}

Type: boolean

.currentParams

Current parameter values.

const params = MyModal.currentParams
console.log('Current user:', params.userId)

Type: Params

.stackIndex

Position in the portal stack (z-index).

const zIndex = MyModal.stackIndex
console.log('Portal z-index:', zIndex)

Type: number (returns -1 if not in stack)

.hasPendingRequest

Whether there's a pending request waiting for resolution.

if (MyModal.hasPendingRequest) {
console.log('Waiting for user response...')
}

Type: boolean

.id

Unique identifier for the portal.

console.log('Portal ID:', MyModal.id)

Type: string

.ref

React ref to the wrapper component.

// Access native methods (for BottomSheet)
MyBottomSheet.ref.current?.snapToIndex?.(0)

Type: React.RefObject<RefType>

Content Props

When you define content with .content(), the render function receives these props:

MyModal.content((props) => {
const {
// State
visible, // boolean - current visibility
params, // Params - current parameters (also destructured)

// Control methods
toggle, // () => Promise<void> - toggle visibility
close, // () => Promise<void> - close portal
open, // (params?) => Promise<void> - open portal
setParams, // (next) => void - update params

// Request system
request, // { resolve, reject } | null - if opened via .request()

// Deprecated (use toggle instead)
nextOrToggle, // () => Promise<void> - alias for toggle
previousOrToggle, // () => Promise<void> - alias for toggle

// Ref
ref, // React.RefObject<RefType> - wrapper ref

// Your custom parameters (destructured)
...yourParams // All params from .open() or .request()

} = props

return <div>Content</div>
})

Using Request Props

const ConfirmModal = modal()
.content((props) => {
const { request, message } = props

if (!request) {
return null // Not opened via .request()
}

return (
<div>
<p>{message}</p>
<button onClick={() => request.resolve(true)}>Yes</button>
<button onClick={() => request.reject('cancelled')}>No</button>
</div>
)
})

// Usage
try {
const confirmed = await ConfirmModal.request({ message: 'Continue?' })
if (confirmed) {
// User clicked Yes
}
} catch (error) {
// User clicked No
console.log('Cancelled:', error)
}

Global Outlet

Portal.GlobalOutlet()

Renders all non-independent portals. Place once in your app root.

import { Portal } from '@codeleap/portals'

function App() {
return (
<div>
<YourAppContent />
<Portal.GlobalOutlet />
</div>
)
}

Portal Registry

Access Portal Registry

import { Portal, Modal } from '@codeleap/portals'

// Access global registry
const allPortals = Portal.registry.getAll()
const userModal = Portal.registry.getInstance('USER_MODAL')

// Modal-specific registry
const modalInstance = Modal.registry.getInstance('MY_MODAL')

// Get stack index
const index = Portal.registry.getStackIndex('USER_MODAL')

// Filter portals
const openPortals = Portal.registry.filter(p => p.isVisible)

Registry Methods

MethodDescription
register(id, instance)Register a portal instance
unregister(id)Remove a portal from registry and stack
getInstance(id)Get portal instance by ID
push(id)Add portal to top of stack
remove(id)Remove portal from stack
getStackIndex(id)Get z-index position of portal
getAll()Get all registered portals
filter(predicate)Filter portals by condition
clear()Clear all portals

Component Rendering

.Component(props?)

Renders the portal as a React component. Useful for independent portals.

const IndependentModal = modal({ independent: true })
.content(() => <div>Independent modal</div>)

function App() {
return (
<div>
{/* Render independent portal manually */}
<IndependentModal.Component />

{/* Other portals via outlet */}
<Portal.GlobalOutlet />
</div>
)
}

// Pass params and portalProps
<IndependentModal.Component
userId="123"
portalProps={{ title: 'User Modal' }}
/>