Skip to content
kitn AI/UI

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.

MechanismWhat it controlsHow you reach it
SlotsPosition — your markup in a named region<div slot="name">…</div>
::part + tokensStyling — the kit’s own regions, from outsidekai-x::part(name) { … }, CSS custom properties
PropsData — per-item content the kit rendersa 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.

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, and footer are inject slots.
  • replace — your markup stands in for the whole region, and you own that region’s data and events. The composer slot is replace: project your own input and you drive submit and loading yourself, feeding the thread through messages.

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.

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.

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.

YOU WRITE — light DOM<kai-chat><nav slot=“sidebar”><header slot=“header”><form slot=“composer”><footer slot=“footer”></kai-chat>el.messages = [ … ]data — a prop, not a slotprojects intoWE RENDER — shadow DOMsidebarheader · replacemessages → cardsdata · card-registrycomposer-actions · injectcomposer · replaceINJECTyour markup is added to the region (a slot)REPLACEyour markup stands in; you own its eventsDATAa prop — never a slot
  • 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::part for 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.