React plugin slots
This page shows React integration for plugin slots.
Use React slots when registered plugins must return ReactNode values for host-defined regions. The host owns the
layout and slot types. Plugins receive only the context and props that the host supplies.
Start with Plugin slots for the shared registry, mode, ordering, and error model.
What React adds#
createReactSlotRegistry(renderer, context, options?): Creates a registry forReactNodevalues.Slot<TSlots, TContext>: Renders a slot from the requiredregistryprop.createSlot(registry, options?): Returns a registry-bound<Slot />component.ReactPlugin<TSlots, TContext>: Describes a plugin that contributesReactNodevalues.
createReactSlotRegistry accepts the shared SlotRegistryOptions. Register
plugins directly with registry.register(). Unlike Core, React does not need a registration wrapper or managed
renderable ownership hooks.
React hosts use @opentui/react/runtime-plugin-support when contributions come from Bun runtime-loaded TSX modules.
See Load plugins and modules at runtime for setup and module maps.
Basic usage#
import { createCliRenderer } from "@opentui/core"
import { createReactSlotRegistry, createRoot, Slot } from "@opentui/react"
type Slots = {
statusbar: { user: string }
}
const context = { appName: "react-app", version: "1.0.0" }
const renderer = await createCliRenderer()
const registry = createReactSlotRegistry<Slots, typeof context>(renderer, context)
const unregister = registry.register({
id: "clock-plugin",
slots: {
statusbar(ctx, props) {
return <text>{`${ctx.appName}:${props.user}`}</text>
},
},
})
const AppSlot = Slot<Slots, typeof context>
function App() {
return (
<AppSlot registry={registry} name="statusbar" user="sam" mode="replace">
<text>fallback-statusbar</text>
</AppSlot>
)
}
createRoot(renderer).render(<App />)Optional convenience helper#
Bind a registry once when you do not want to pass it to each slot:
const AppSlot = createSlot(registry)<Slot> props#
| Prop | Type | Required | Description |
|---|---|---|---|
registry |
SlotRegistry<ReactNode, Slots, Context> |
yes | Registry to resolve plugins from |
name |
keyof Slots |
yes | Which slot to render |
mode |
SlotMode |
no | "append" (default), "replace", or "single_winner". See slot modes. |
pluginFailurePlaceholder |
(failure: PluginErrorEvent) => ReactNode |
no | Per-slot placeholder UI when a plugin throws |
children |
ReactNode |
no | Fallback UI |
| remaining | Slots[name] |
varies | Slot-specific props forwarded to plugin renderers |
ReactSlotOptions (for createSlot)#
| Option | Type | Required | Description |
|---|---|---|---|
pluginFailurePlaceholder |
(failure: PluginErrorEvent) => ReactNode |
no | Creates placeholder UI when a plugin throws |
Plugin failure placeholders#
const Slot = createSlot(registry, {
pluginFailurePlaceholder(failure) {
return <text>{`plugin-error:${failure.pluginId}:${failure.phase}`}</text>
},
})If a contribution throws, the slot renders the placeholder. In single_winner mode, the slot uses children if no
placeholder exists or the placeholder returns null.
In replace mode, an initial contribution failure without a usable placeholder adds no output. If every initial
contribution fails this way, the slot uses children.
A later subtree failure uses children in single_winner mode and in replace mode with one contribution. In
replace mode with multiple contributions, only the failed subtree disappears.
The slot catches a failure from the initial contribution call directly. A per-plugin React error boundary catches
failures from the rendered subtree. The boundary resets when the registry changes. If the placeholder throws, the
registry reports an error_placeholder failure and treats the placeholder as unavailable.
Lifecycle and disposal#
The <Slot> component subscribes to registry changes in a React effect. Unmounting the component removes that
subscription and unmounts its contribution subtrees. React owns the lifecycle of the returned ReactNode values.
When a component registers a plugin, return the unregister function from its effect:
import { useEffect } from "react"
useEffect(() => {
return registry.register({
id: "clock-plugin",
slots: {
statusbar: () => <text>clock</text>,
},
})
}, [registry])Unregistration calls the plugin’s dispose hook. Renderer destruction clears the registry and also calls dispose.
Unlike Core managed contributions, React contributions do not receive onActivate, onDeactivate, or onDispose
node hooks.
Example#
See the React slot failure example.