Solid slots
This page shows Solid integration for plugin slots.
Use slots when you want external modules to contribute JSX.Element UI in host-defined regions without forking your app. The host keeps ownership of layout and slot typing; plugins only see the context and props you expose.
If you have not read the shared model yet, start with Plugin Slots.
Runtime-loaded external plugins
If your app loads plugins from disk at runtime (for example await import(fileUrl)), add this import once in your app entry:
import "@opentui/solid/runtime-plugin-support"
This is a Bun drop-in that installs runtime transform support so external TS/TSX plugin modules use the same runtime instances as the host app (@opentui/solid, @opentui/core, @opentui/core/testing, solid-js, and solid-js/store). If a plugin imports another OpenTUI package, provide that package through additional runtime modules.
Use this for plugin systems in both normal Bun runs and standalone compiled executables.
import "@opentui/solid/runtime-plugin-support"
const mod = await import(pathToFileURL(pluginPath).href)
registry.register(mod.loadExternalPlugin())
If plugins need additional host-resolved modules, configure runtime support before the side-effect import is loaded:
import { ensureRuntimePluginSupport } from "@opentui/solid/runtime-plugin-support/configure"
ensureRuntimePluginSupport({
additional: {
"my-runtime-module": { marker: "ok" },
},
})
First-party packages expose side-effect-free runtime module maps. For keymap-aware Solid plugins:
import { runtimeModules as keymapRuntimeModules } from "@opentui/keymap/runtime-modules"
import { ensureRuntimePluginSupport } from "@opentui/solid/runtime-plugin-support/configure"
ensureRuntimePluginSupport({
additional: keymapRuntimeModules,
})
For plugins that import @opentui/three, install @opentui/three in the host app and pass its runtime modules explicitly:
import { runtimeModules as threeRuntimeModules } from "@opentui/three/runtime-modules"
import { ensureRuntimePluginSupport } from "@opentui/solid/runtime-plugin-support/configure"
ensureRuntimePluginSupport({
additional: threeRuntimeModules,
})
The side-effect import and configurable entrypoint install the same runtime plugin. Late additions throw with a clear error instead of being ignored.
What Solid adds
createSolidSlotRegistry(renderer, context, options?)— create a registry typed forJSX.Element. Accepts the sameSlotRegistryOptionsascreateSlotRegistry.Slot<TSlots, TContext>— generic slot component that takesregistryas a required propcreateSlot(registry, options?)— optional convenience helper that returns a registry-bound<Slot />componentSolidPlugin<TSlots, TContext>— convenience type alias for a plugin that returnsJSX.Element@opentui/solid/runtime-plugin-support— one-line runtime support for external plugin/module loading@opentui/solid/runtime-plugin-support/configure— configurable runtime support without import-time side effects
Register plugins directly with registry.register() — no wrapper function is needed (unlike core’s registerCorePlugin).
Basic usage
import { createCliRenderer } from "@opentui/core"
import { createSolidSlotRegistry, Slot, render } from "@opentui/solid"
type Slots = {
statusbar: { user: string }
}
const context = { appName: "solid-app", version: "1.0.0" }
const renderer = await createCliRenderer()
const registry = createSolidSlotRegistry<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>
const App = () => (
<AppSlot registry={registry} name="statusbar" user="sam" mode="replace">
<text>fallback-statusbar</text>
</AppSlot>
)
render(() => <App />, renderer)
Optional convenience helper
If you prefer not to pass registry each time, you can still bind one once:
const AppSlot = createSlot(registry)
<Slot> props
| Prop | Type | Required | Description |
|---|---|---|---|
registry | SlotRegistry<JSX.Element, 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) => JSX.Element | no | Per-slot placeholder UI when a plugin throws |
children | JSX.Element | no | Fallback UI |
| remaining | Slots[name] | — | Slot-specific props forwarded to plugin renderers |
SolidSlotOptions (for createSlot)
| Option | Type | Required | Description |
|---|---|---|---|
pluginFailurePlaceholder | (failure: PluginErrorEvent) => JSX.Element | 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 plugin throws, the slot renders the placeholder. If no placeholder is provided (or it returns null), the slot falls back to children.
Plugins that throw during the initial render call are caught inline. Plugins that throw during a Solid re-render are caught by an internal <ErrorBoundary> that reports the error to the registry.
Observability
registry.onPluginError((event) => {
console.error(event.pluginId, event.phase, event.source, event.error.message)
})
Example: packages/solid/examples/components/plugin-slots-demo.tsx