react-portalslots
v1.0.6
Published
Tiny React Portal slots (Provider + named Slot & Portal factory).
Downloads
53
Readme
React components often need to render content in different parts of the UI tree. Traditional approaches lead to prop drilling, tight coupling, and layout constraints.
react-portalslots provides:
- Decoupled rendering: Render content anywhere, display it elsewhere
- Named slots: Semantic slots (header, footer, sidebar) that components can target
- Type safety: Full TypeScript support
- Minimal API: Just a provider and factory function
Perfect for layout systems, component libraries, and avoiding prop drilling.
Installation
npm install react-portalslots
# or
pnpm add react-portalslots
# or
yarn add react-portalslots
# or
bun add react-portalslotsUsage
import { PortalSlotsProvider, PortalSlot } from 'react-portalslots';
const HeaderPortal = PortalSlot('header');
const FooterPortal = PortalSlot('footer');
function Layout({ children }: { children: React.ReactNode }) {
return (
<div className="page">
<header className="page-header">
<HeaderPortal.Slot />
</header>
<main className="page-content">{children}</main>
<footer className="page-footer">
<FooterPortal.Slot />
</footer>
</div>
);
}
export function App() {
return (
<PortalSlotsProvider>
<Layout>
{/* These can live anywhere in the tree */}
<HeaderPortal>
<button>Save</button>
</HeaderPortal>
<FooterPortal>
<small>© 2025</small>
</FooterPortal>
{/* App content */}
<div>Dashboard</div>
</Layout>
</PortalSlotsProvider>
);
}Without this library
import React from 'react';
type LayoutProps = {
header?: React.ReactNode;
footer?: React.ReactNode;
children: React.ReactNode;
};
function Layout({ header, footer, children }: LayoutProps) {
return (
<div className="page">
<header className="page-header">{header}</header>
<main className="page-content">{children}</main>
<footer className="page-footer">{footer}</footer>
</div>
);
}
export function App() {
// Content that wants to render into the header/footer must be lifted up here
// from deep components, causing prop drilling and tight coupling.
return (
<Layout
header={<button>Save</button>}
footer={<small>© 2025</small>}
>
<SomeToolbar />
</Layout>
);
}
function SomeToolbar() {
// Cannot push content into the header without threading callbacks/state
// through multiple layers or using a global store (which is brittle).
return null;
}- Drawbacks: prop drilling, implicit coupling, awkward lifting of state/UI, hard reuse/testing.
API
PortalSlotsProvider
Context provider that must wrap your application.
<PortalSlotsProvider>
<App />
</PortalSlotsProvider>PortalSlot(name?: string)
Factory function that creates a pair of components for a named slot.
- PortalSlot.Slot: The slot container where content will be rendered
- PortalSlot: Portal component that renders content into the slot
const HeaderPortal = PortalSlot('header');
// Use the slot in your layout
<HeaderPortal.Slot />
// Render content into the slot from anywhere
<HeaderPortal>
<button>Save</button>
</HeaderPortal>License
MIT
