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.
npm install --save-exact @grlt-hub/react-slotspnpm add --save-exact @grlt-hub/react-slotsyarn add --exact @grlt-hub/react-slotsbun add --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> </> )}