Skip to content
A UI wireframe with named slots — header, sidebar, core, footer — into which plugin modules fly in and dock. A UI wireframe with named slots — header, sidebar, core, footer — into which plugin modules fly in and dock.

React Slots

Build extensible React applications with slot-based architecture. Declare named extension points and inject content into them from anywhere — without editing the components that declare them.
npm install --save-exact @grlt-hub/react-slots

React has no built-in slot system. When you build extensible apps — dashboards with plugins, host shells with third-party modules — you fall back on prop drilling, ad-hoc context, or render props. React Slots gives you named extension points and lets content be injected into them from anywhere in your codebase.

Inject from anywhere

Plugins and separate packages call insert() to dock components into a slot — from anywhere, with no prop drilling and no coupling between the host and what fills it.

Control what renders

Decide what shows and in what shape: order for render order, filter to gate rendering, and mapProps to reshape the props a slot passes down.

Know what's in a slot

React to slot contents with useCount and usePresence — e.g. render a heading only when the slot actually has visible content.

Type-safe & lightweight

Fully typed slot props out of the box, a runtime under ~2 KB gzipped, and React 16.8 → 19 support.

The core knows nothing about what fills it — content is injected from outside, and order decides the sequence. Edit the code; the preview re-runs as you type.

import { createSlot } from "@grlt-hub/react-slots"
import { Issues } from "./issues"
import { PullRequests } from "./pull-requests"
import { Stars } from "./stars"
const widgets = createSlot()
widgets.api.insert({ Component: Issues })
widgets.api.insert({ Component: PullRequests })
widgets.api.insert({ Component: Stars })
export default function App() {
return (
<>
<aside>
<h2>dashboard</h2>
<widgets.Root />
</aside>
</>
)
}
import { createSlot } from "@grlt-hub/react-slots"
import { Issues } from "./issues"
import { PullRequests } from "./pull-requests"
import { Stars } from "./stars"
const widgets = createSlot()
widgets.api.insert({
Component: PullRequests,
order: 1,
})
widgets.api.insert({
Component: Issues,
order: 2,
})
widgets.api.insert({
Component: Stars,
order: 0,
})
export default function App() {
return (
<>
<aside>
<h2>dashboard</h2>
<widgets.Root />
</aside>
</>
)
}
import { createSlot } from "@grlt-hub/react-slots"
import { Issues } from "./issues"
import { PullRequests } from "./pull-requests"
import { Stars } from "./stars"
const widgets = createSlot<{ stars: number; issues: number; prs: number }>()
widgets.api.insert({
mapProps: (props) => ({ value: props.issues }),
Component: Issues,
})
widgets.api.insert({
mapProps: (props) => ({ value: props.prs }),
Component: PullRequests,
})
widgets.api.insert({
mapProps: (props) => ({ value: props.stars }),
Component: Stars,
})
export default function App() {
return (
<aside>
<h2>dashboard</h2>
<widgets.Root stars={1240} issues={24} prs={5} />
</aside>
)
}
import { createSlot } from "@grlt-hub/react-slots"
import { useEffect, useState } from "react"
import { Stars } from "./stars"
const widgets = createSlot<{ stars: number }>()
widgets.api.insert({
filter: (props) => props.stars % 2 === 0,
mapProps: (props) => props,
Component: (props) => <Stars value={props.stars} />,
})
export default function App() {
const [stars, setStars] = useState(10)
useEffect(() => {
const id = setInterval(() => setStars((s) => s + 1), 1000)
return () => clearInterval(id)
}, [])
return (
<>
<aside>
<h2>dashboard</h2>
<widgets.Root stars={stars} />
</aside>
</>
)
}