When working on complex UI layouts, you often encounter scenarios where a component must render in a different part of the DOM tree than where it is declared. This is where the Outlet Pattern becomes a powerful technique. Unlike patterns tied to routing (like React Router's Outlet), this approach is layout-agnostic and purely structural — it helps you control where in the DOM a component should render, without tight coupling to its declaration point.
In this article, we’ll explore how the Outlet Pattern works, when you should consider using it, and how to implement it with React Portals.
The Problem: Component Misalignment
Consider a situation where you’re building a design system component — for example, a Bulk Actions Bar that appears when items are selected. You want this bar to:
- Render at a specific position within a content layout.
- Be decoupled from its logical parent component (e.g., a Table or List component).
- Avoid issues with CSS stacking context and layout constraints.
If you render the bar inline, it might break the layout flow. You need a way to teleport its rendered output to a precise location in the DOM — but still control it declaratively in React.
The Solution: Outlet Pattern with React Portals
The Outlet Pattern introduces a placeholder (Outlet component) in the desired DOM location and a render controller (OutletProvider) that allows components elsewhere in the tree to render into that placeholder.
The Key Pieces:
- Outlet Component — A placeholder that provides a DOM node reference.
- OutletProvider/Context — Shares the target DOM node with components that want to render into the Outlet.
- Portals — Used by child components to render into the provided DOM node.
Implementing the Outlet Pattern
Step 1: Create the Outlet Context
import { createContext, useContext, useRef, useState } from 'react'
const OutletContext = createContext(null)
export function useOutlet() {
const context = useContext(OutletContext)
if (!context) {
throw new Error('useOutlet must be used within an OutletProvider')
}
return context
}
Step 2: Build the Outlet Component
export function Outlet() {
const [, setNode] = useOutlet()
const callbackRef = useCallback((nodeRef) => {
if(nodeRef) {
setNode(nodeRef)
}
}, [setNode])
return <div ref={callbackRef} />
}
Step 3: Provide the Outlet Context
export function OutletProvider({ children }) {
const [node, setNode] = useState(null)
return (
<OutletContext.Provider value={[node, setNode]}>
{children}
</OutletContext.Provider>
)
}
Step 4: Rendering into the Outlet
import { createPortal } from 'react-dom'
export function BulkActionsBar() {
const [node] = useOutlet()
if (!node) return null
return createPortal(
<div className="bulk-actions-bar">I’m rendered in the Outlet!</div>,
node
)
}
Usage Example
<OutletProvider>
<Layout>
<MainContent />
<Outlet />
</Layout>
<BulkActionsBar />
</OutletProvider>
When Should You Use the Outlet Pattern?
- You need to render floating elements (toolbars, modals, bulk action bars) into a specific layout area.
- You want a clean separation of component logic vs layout positioning.
- You need a flexible layout structure without deep prop drilling.
Conclusion
The Outlet Pattern is a simple yet powerful technique when you need to control where a component renders in the DOM, independently from its logical position in the React tree. Combined with React Portals, it becomes an elegant solution for layout composition challenges.
Top comments (0)