Two Ways to Let an App Differ
The shared UI chrome — the panel that docks an assistant surface into an app — has to accommodate more than a dozen hosts with different visual identities, different explanations of their own controls, and different platforms. It does that with exactly two kinds of extension point, and the distinction is worth holding.
- A prop is a value you pass in: the embed URL, a storage key prefix, the header wording, an emblem. The shared component knows the shape of what it will receive and where it goes.
- A slot is a region the app fills with whatever it renders. The shared component knows only where it goes, and nothing about what it is.
Props are cheaper to reason about and should be the default. Slots are for the cases where anticipating the shape is the mistake.
The Case That Proved the Slot
Every host puts a sentence in the panel's unavailable state: the assistant is offline, try again. One host is different — a desktop app that ships a local model, so when the remote assistant is unreachable it can still hold a conversation. It wanted an entire interactive chat surface where the others put one line.
The shared component needed no change at all. The slot already accepted arbitrary content; the app supplied its offline surface and un-centered the slot's default layout in its own stylesheet. A prop would have had to anticipate this — an offlineMessage string could not hold a chat interface, and by the time you have generalized it enough to, you have invented a slot with extra steps.
When One Seam Is Not Enough
A second slot shape had to be added later, and the reason is instructive because it is about the DOM rather than about design. Apps explain their controls two different ways. Some pass attributes onto a control — a title, an aria description, extra handlers. One renders a tooltip component that produces a sibling node beside the control, which cannot be expressed as attributes on that control no matter how many attributes you allow.
So there are two seams: merge attributes onto a control, or wrap a control in the app's own node. Both optional, neither disturbing the consumers that use neither. The lesson is not that you should ship two — it is that the second one was added when a real case proved that the first could not express it, rather than pre-emptively because two sounded more flexible.