Portal
The @bento/portal package exports the Portal component, which renders
children into a target DOM container outside the normal React component
hierarchy. Portal solves common UI challenges like z-index conflicts, clipping
issues, and overlay stacking by rendering content to document.body or a custom
container.
Portal integrates seamlessly with React ARIA's UNSAFE_PortalProvider while
remaining fully independent and functional without it.
Use Cases
Portal is essential for UI elements that need to escape their parent container's boundaries:
- Overlays and Modals: Prevent clipping and z-index conflicts
- Dropdown Menus: Render above all other content regardless of parent constraints
- Tooltips: Position freely without overflow hidden issues
- Toast Notifications: Display at the application level
- Popovers and Dialogs: Ensure proper stacking and positioning
Installation
npm install --save @bento/portal
Props
The @bento/portal package exports the Portal component:
<Source language='tsx' code={ BasicExample } />
The following properties are available to be used on the Portal component:
container | Element | No | The container to render the portal content into. |
| If not provided, Portal will check for React ARIA's PortalProvider, | | | |
| and fall back to document.body. | | | |
mounted | boolean | No | Should the portal content be mounted. |
| Set to false by default for server-side rendering compatibility. | | | |
| Portal will not render children if not mounted. | | | |
children | ReactNode | ((data: { props: { mounted?: boolean; }; }) => ReactNode) | Yes | The content to render inside the portal. |
| Can be a React node or a render prop function that receives props. | | | |
slot | string | No | A named part of a component that can be customized. This is implemented by the consuming component. |
| The exposed slot names of a component are available in the components documentation. | | | |
slots | Record<string, object | Function> | No | An object that contains the customizations for the slots. |
| The main way you interact with the slot system as a consumer. | | | |
Examples
Basic
Basic portal usage that renders content to document.body:
<Source language='tsx' code={ BasicExample } />
Custom Container
Portal can render to a custom container instead of document.body. This is
useful when you need to control the exact DOM location where portal content
appears:
<Source language='tsx' code={ CustomContainerExample } />
React ARIA Integration
Portal optionally integrates with React ARIA's UNSAFE_PortalProvider to
provide consistent portal container management across your application. This
example demonstrates how Portal automatically detects and uses the
PortalProvider's container:
<Source language='tsx' code={ PortalProviderExample } />
Container priority:
- Explicit
container prop (highest priority)
- React ARIA's
UNSAFE_PortalProvider container (via useUNSAFE_PortalContext)
document.body (fallback)
Benefits:
- Stacking context: All overlays share the same stacking context
- Custom location: Control where overlays render in DOM
- Multiple overlays: Proper stacking when multiple portals/overlays are open
- React ARIA compatibility: Works seamlessly with React ARIA components
- Graceful degradation: Works without React ARIA installed
SSR Safety
Portal is SSR-safe and will not render content on the server. The mounted prop
should be set to true only after the component has mounted on the client:
function MyComponent() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
return (
<Portal mounted={mounted}>
<Container>Content</Container>
</Portal>
);
}
Accessibility
Portal is designed with accessibility in mind:
- Semantic HTML: Portal doesn't interfere with semantic HTML structure
- Screen Reader Support: Content rendered through Portal remains accessible to screen readers
- Focus Management: Portal doesn't trap or redirect focus - implement your own focus management as needed for modals/dialogs
- Keyboard Navigation: Portal preserves keyboard navigation of its content
- ARIA Support: All ARIA attributes applied to portal content are preserved
Note: Portal is a rendering utility and doesn't provide built-in focus
*trapping or modal behavior. For accessible modals and dialogs, combine Portal
*with proper focus management and ARIA attributes.
Data Attributes
Portal applies the following data attributes to portal content:
data-portal | Marks content rendered via portal | "true" |
data-mounted | Whether portal content has mounted | "true" / "false" |
Customization
Portal is a rendering utility component that doesn't render its own DOM
elements. Instead, it renders children into a target container using React's
createPortal. Because of this, Portal doesn't introduce internal slots like
other Bento components.
Slots
Portal is created using the @bento/slots package via withSlots('BentoPortal', ...), which means it supports the standard slot and slots props for
integration with parent components. However, Portal itself doesn't define any
internal slot assignments since it acts as a transparent rendering boundary.
See the @bento/slots package for more information on how to use the slot and
slots properties.
Styling
Portal applies data attributes to the children it renders, making it possible to
style portal content based on its state. Since Portal doesn't render wrapper
elements, you should apply className or style props directly to the children
you pass to Portal.
The data attributes Portal applies can be used in your CSS selectors:
[data-portal="true"] {
}
[data-portal="true"][data-mounted="true"] {
animation: fadeIn 200ms ease-in;
}
When using Portal with other Bento components like Container, you can apply
styling through those components:
<Portal mounted={mounted}>
<Container className="my-overlay">
<Text>Styled portal content</Text>
</Container>
</Portal>
The data attributes will be applied to the Container element, allowing you to
target it:
.my-overlay[data-portal="true"] {
}