How composition works
The kit owns the frame — where things go — and the primitives. You own the content and the wiring. There’s no enum of every layout to pick from; you compose the one you need from three mechanisms. Learn which mechanism does what, and the patterns that follow read as obvious.
The three mechanisms
Section titled “The three mechanisms”| Mechanism | What it controls | How you reach it |
|---|---|---|
| Slots | Position — your markup in a named region | <div slot="name">…</div> |
::part + tokens | Styling — the kit’s own regions, from outside | kai-x::part(name) { … }, CSS custom properties |
| Props | Data — per-item content the kit renders | a JavaScript property (messages, cards) |
One rule decides between them: a slot moves your markup; a ::part restyles ours; a prop hands us the data and lets us render each item. Reach for a prop only for behavior CSS can’t express.
1. Slots — position
Section titled “1. Slots — position”A slot is a named hole inside the element’s shadow that you fill with your own markup. Set the slot attribute on a direct child:
<kai-chat> <nav slot="sidebar">…your conversation list…</nav> <footer slot="footer">Responses may be inaccurate.</footer></kai-chat>Inside a slot, nothing is kai-*-bound — it’s your <nav>, your <form>, your CSS. A slot only earns its place where the shadow boundary blocks you. Content above or below the whole element is your own layout (a sibling node), so there’s no slot for it.
Slots come in two modes:
- inject — additive. The built-in region still renders; your markup is added to it.
header-start,composer-actions, andfooterare inject slots. - replace — your markup stands in for the whole region, and you own that region’s data and events. The
composerslot isreplace: project your own input and you drive submit and loading yourself, feeding the thread throughmessages.
2. ::part and tokens — styling
Section titled “2. ::part and tokens — styling”To restyle a region the kit renders, target its ::part from your own stylesheet. No shadow piercing, no !important arms race:
kai-chat::part(header-bar) { height: 3.5rem; padding-inline: 1rem }kai-message::part(bubble) { background: var(--color-primary) }Parts also cover the “just hide it” case, which is pure CSS and so deliberately not a prop — for example, an Enter-only prompt input:
kai-prompt-input::part(send) { display: none }For global look-and-feel — color, radius, type — set the design tokens instead of styling every part. See Theming.
3. Props — per-item data
Section titled “3. Props — per-item data”Per-item content stays a property, never a slot. Messages, generative-UI cards, model lists — you hand the data to the element and it renders each item.
chat.messages = [ { id: '1', role: 'assistant', content: 'How can I help?' },];This is the load-bearing constraint, not an arbitrary choice: a slotted light-DOM node lives outside the shadow and can’t read the component’s reactive state, so it can’t re-render per message or per card. That’s why custom card UI registers in the card registry (the kit renders it with the item’s data) rather than being slotted in. See Generative UI.
You write → we render
Section titled “You write → we render”The split, end to end on <kai-chat>: your light DOM projects into named slots; the data you set as a property drives what the kit renders inside its shadow.
When to reach for which
Section titled “When to reach for which”- Move or replace a region → a slot. Adding chrome (a footer, a toolbar control) is inject; swapping a whole region (your own header or composer) is replace, and you own its wiring.
- Restyle a region the kit renders →
::partfor that one region, design tokens for global look. CSS-only behaviors (hide the send button) are parts, not props. - Change per-item content → a prop or the card registry — never a slot, because a slotted node can’t see the component’s reactive state.
- Outgrown the slots on
<kai-chat>→ step down a layer and assemble the shell from the primitives yourself.
Every element’s exact holes and styleable regions live in its own page under Slots and Styling (e.g. kai-chat, kai-message, kai-prompt-input).
- Compose your own shell — assemble the sidebar, message thread, and composer from the primitives when
<kai-chat>is too fixed. - Custom chat header — inject controls and replace the header in practice.
- Generative UI — the card registry, the data-render path for custom message content.
- Theming — the design tokens behind the styling layer.