react-portalslots
v1.0.6
Published
Tiny React Portal slots (Provider + named Slot & Portal factory).
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
