@radix-ui/react-portal and react-portal are both libraries designed to render React components into a DOM node that exists outside the parent component's DOM hierarchy. This is essential for building modals, tooltips, and popovers that need to escape overflow-hidden containers or z-index stacking contexts. @radix-ui/react-portal is part of the modern Radix UI primitive ecosystem, offering unstyled, accessible components that integrate tightly with React 18+ features. react-portal is a legacy standalone library that pioneered this pattern but has seen less active development in recent years compared to the Radix ecosystem.
Both @radix-ui/react-portal and react-portal solve the same fundamental problem: rendering children into a DOM node outside the current component tree. This is critical for UI elements like modals, dropdowns, and toast notifications that must visually break out of their container's clipping or stacking context. However, they differ significantly in maintenance status, API design, and ecosystem integration.
@radix-ui/react-portal is actively maintained as part of the Radix UI suite.
// @radix-ui/react-portal: Modern React support
import * as Portal from '@radix-ui/react-portal';
function Modal() {
return (
<Portal.Root>
<div className="modal-content">Hello</div>
</Portal.Root>
);
}
react-portal is a legacy library with infrequent updates.
// react-portal: Legacy implementation
import Portal from 'react-portal';
function Modal() {
return (
<Portal isOpened={true}>
<div className="modal-content">Hello</div>
</Portal>
);
}
@radix-ui/react-portal uses a declarative component API with named exports.
<Portal.Root> to render it at the document body by default.container prop to specify a custom target node.// @radix-ui/react-portal: Declarative API
import * as Portal from '@radix-ui/react-portal';
function Tooltip() {
return (
<Portal.Root container={document.getElementById('tooltip-root')}>
<span>Tooltip content</span>
</Portal.Root>
);
}
react-portal uses a single default export component with props.
isOpened prop.node prop to define the target container.// react-portal: Prop-based API
import Portal from 'react-portal';
function Tooltip() {
return (
<Portal isOpened={true} node={document.getElementById('tooltip-root')}>
<span>Tooltip content</span>
</Portal>
);
}
@radix-ui/react-portal is built with TypeScript first.
// @radix-ui/react-portal: Full TypeScript support
import * as Portal from '@radix-ui/react-portal';
// Props are fully typed
<Portal.Root className="custom-class">
{children}
</Portal.Root>
react-portal has community-maintained types or older definitions.
@types/react-portal separately.// react-portal: External types required
import Portal from 'react-portal';
// May require: npm install --save-dev @types/react-portal
<Portal isOpened={true}>
{children}
</Portal>
@radix-ui/react-portal integrates with other Radix primitives.
// @radix-ui/react-portal: Part of a larger system
import * as Dialog from '@radix-ui/react-dialog';
import * as Portal from '@radix-ui/react-portal';
// Used internally by Dialog, but available for custom composition
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content />
</Dialog.Portal>
react-portal is a standalone solution.
// react-portal: Standalone usage
import Portal from 'react-portal';
// No built-in integration with other UI libraries
<Portal isOpened={isOpen}>
<CustomModal />
</Portal>
@radix-ui/react-portal is safe for long-term use.
// @radix-ui/react-portal: Future-proof
// No expected breaking changes in the near term
react-portal carries higher maintenance risk.
// react-portal: Evaluate alternatives
// Consider migration path to Radix or native React.createPortal
Despite their differences, both libraries share core functionality and goals.
overflow: hidden or z-index issues.// Both solve this problem
// @radix-ui/react-portal
<Portal.Root>{content}</Portal.Root>
// react-portal
<Portal isOpened>{content}</Portal>
// Both use standard React patterns
function App() {
const [open, setOpen] = useState(false);
return open && <PortalComponent />;
}
body.// @radix-ui/react-portal
<Portal.Root container={ref.current} />
// react-portal
<Portal node={ref.current} />
| Feature | @radix-ui/react-portal | react-portal |
|---|---|---|
| Maintenance | ✅ Active, Regular Updates | ⚠️ Legacy, Infrequent Updates |
| React 18 Support | ✅ Full Support | ⚠️ May Require Workarounds |
| TypeScript | ✅ Built-in, First-class | ⚠️ Community Types |
| Ecosystem | 🧩 Part of Radix UI Primitives | 🧍 Standalone |
| API Style | 🧱 Named Exports (Portal.Root) | 🧱 Default Export (Portal) |
| Recommendation | ✅ Preferred for New Projects | ⚠️ Legacy Maintenance Only |
@radix-ui/react-portal is the modern standard 🏆 — it offers better TypeScript support, active maintenance, and seamless integration with contemporary React features. It is the clear choice for building robust, accessible design systems.
react-portal is a legacy tool 🕰️ — it served the community well for many years but is now overshadowed by more integrated solutions. Use it only if you are maintaining existing code that depends on it.
Final Thought: While react-portal pioneered the pattern, @radix-ui/react-portal represents the current best practice. For new development, prioritize libraries that align with the modern React ecosystem to reduce technical debt over time.
Choose @radix-ui/react-portal for new projects, especially if you are already using Radix UI primitives or require strict accessibility compliance. It is actively maintained, fully typed with TypeScript, and designed to work seamlessly with React 18's concurrent features. This is the standard choice for modern design systems and component libraries.
Choose react-portal only if you are maintaining a legacy codebase that already depends on it and migration costs are too high. It is generally not recommended for new development due to slower update cycles and less integration with modern React ecosystems. Consider migrating to @radix-ui/react-portal for better long-term support.
react-portalView docs here.