Skip to content
LogoLogo

FAQs

Can one slot have several hosts?

Yes, any number, mounted at once. Each renders the same contributions and hands them its own props — which is what makes one contribution adapt per row of a table. See Slots, hosts & fills.

What renders when nothing is contributed?

The host's own children. There is no separate placeholder API.

Note the exact wording: children render while nothing is contributed, not while nothing produced output. Three contributions that all return null mean the host has contributions, so the placeholder stays hidden — and the container is genuinely empty, so CSS covers it.

Does it work with server rendering?

The registry does, fully: contributions are in the HTML, with no effects on the render path. The only requirement is that the resolver gets the same inputs, in the same order, on both sides.

The runtime channel does not. Its server and first hydration snapshots are both empty; fills register from an effect after hydration, replacing each façade host's placeholder. See Server rendering.

Two contributions with the same order?

Both render. The tie is stable: plugin position in the list, then declaration position inside the plugin. Nothing is overwritten and nothing is dropped. On the façade, fills that share an order render in mount order.

Why is useSlotProps() nullable?

Because nothing guarantees a host above its caller. The createSlot façade's useProps is the non-nullable one, since a Slot's children only ever render inside one of its factory's hosts.

Inside a declared contribution you rarely need either: the host's props arrive as your component's own props, already narrowed.

How is this different from a portal?

A portal moves DOM nodes. create-slot composes UI logically: the contribution is rendered by the host, inside the host's React tree, so it sees the host's context and its props. Nothing is relocated in the DOM after the fact.

How is this different from pushing elements through context?

You are not maintaining an array of elements in a global context, and the host is not re-rendering the world when one feature changes. The registry resolves once into plain data and compares entries by content; the façade reads its private store with useSyncExternalStore.

Do I need SlotProvider for createSlot?

No. The façade has no provider: its host renders its factory's fills — or its placeholder — anywhere. SlotHost throws without a SlotProvider, because there a missing provider is a mistake worth reporting.

Can I use both channels in the same slot?

No — and that is the point. A SlotHost renders declared contributions only, and a façade host renders its factory's fills only, so a late client fill can never displace markup the server already shipped. Use both channels side by side, each with its own host. See Two channels.

How do I make exactly one plugin own something?

Not with a slot. "Exactly one owner" is a routing problem — resolve the claims into one table before render, where your code knows the keys and can report the plugin it refused. See Exclusive claims.

Can a contribution be conditional?

Yes: return null. There is no when predicate, and a contribution is an ordinary component, so the condition can read hooks, context, a store or a flag — which a predicate in the manifest could not.

What does the library validate?

Two throws at declaration — defineSlot and definePlugin need a non-empty name and id — and one at render: a façade fill's child must be a single element.

Everything about the manifest comes back as diagnostics on the Resolution instead: duplicate plugin ids, duplicate or invalid contribution ids, disable and override targets that nothing carries. expect(resolvePlugins(PLUGINS).diagnostics).toEqual([]) in a test is the catalog validator. Names, capabilities, routes and versions are your manifest's fields, so they are yours to check.

Does a plugin's contributions share state?

On the runtime channel, yes: the feature is one subtree, so ordinary useState works. On the declarative channel, no — the contributions are separate places in the tree that carry the same pluginId, and a store takes the subtree's place. See Plugin state.

Does one feature's state change re-render the others?

No, as long as the host's own props did not really change. The host compares its props by value, compares each resolved entry by content, and renders each contribution through a memoised view of it — so a hundred contributions do not re-render because a search box beside them took a keystroke, and even an inline resolvePlugins() per render re-renders only hosts and boundary shells. See Performance.

Is a slot name a global?

A declared slot name is a key in whatever Resolution you resolve — scope it by resolving different graphs for different providers. The façade has no names at all: each createSlot() factory owns a private store, so two factories can never exchange fills, and a duplicated copy of the package cannot split a store it does not hold.

Which React versions work?

18 and up, on any renderer. The only React API the library needs that 17 lacks is useSyncExternalStore. React is a peer dependency, and the package has no dependencies of its own. create-slot/core needs no React at all.

Can a contribution use hooks, context and data fetching?

Yes. A contribution is an ordinary component rendered by the host, inside the host's React tree, so every context above the host is above it too. A declared contribution is additionally wrapped in Suspense, so it may read a promise the layout never awaited — that is what makes per-contribution streaming work. The fallback is the provider's Pending, or null when none is set. See Server rendering.

Does a plugin that is turned off stay in my bundle?

Its manifest does. Its components do not, if you split them.

On the runtime channel a plugin is a module, so lazy moves all of it into one chunk, and a plugin you never mount is a chunk the browser never requests. On the declarative channel the manifest has to stay: the application reads it to resolve the graph and to assemble state before render. Only the components move. See Recipes.

Does it work with React Server Components?

Yes, in two tiers. create-slot/core never imports React, so a server component can import manifests and call resolvePlugins itself — and under the two-module discipline the Resolution it returns crosses the client boundary whole, as metadata plus client references. The simpler tier keeps manifests behind one "use client" module and sends ids across. See React Server Components.

How do I test a contribution?

Render the component directly with the props the host would give it — no provider needed. To test the wiring, resolve and render the host inside a SlotProvider; to validate the catalog, assert diagnostics is empty. See Testing.

What happens if a contribution throws?

A declared one is caught by the boundary around it: onError reports it with the plugin id, contribution id and slot name, and the Failed component renders in its place. Recovery is the explicit reset() — there is no automatic retry.

A façade fill gets a Suspense boundary with a null fallback so suspension cannot tear down its own registration. It gets no error boundary: it is your own code in your own tree. See Failure isolation.

Where do I find an error message?

Every message the library throws or reports is on one page, with its cause and its fix — see Errors. They all start with [create-slot].