# Compose your own shell

Step down from <kai-chat> and assemble the sidebar, message thread, and composer yourself for complete layout control.

When `<kai-chat>` doesn't fit your layout — you need a third panel, a draggable sidebar, or a bespoke header — compose the shell from its leaf components. You own the data flow; the components handle the rendering.

## How it works

Three real `kai-*` web components inside a `<kai-resizable>` layout:

```html
<kai-resizable orientation="horizontal" style="display:block;height:560px">

  <!-- Sidebar: conversation list -->
  <kai-resizable-item size="22%" min="160px" max="40%">
    <kai-conversations id="list" style="display:block;height:100%"></kai-conversations>
  </kai-resizable-item>

  <!-- Main: scrolling thread + composer -->
  <kai-resizable-item>
    <div class="chat-col">
      <div id="thread" class="messages"></div>
      <kai-prompt-input id="input" placeholder="Message the assistant…"></kai-prompt-input>
    </div>
  </kai-resizable-item>

</kai-resizable>

<script type="module">

  await customElements.whenDefined('kai-conversations');

  // Assign arrays and objects in JavaScript — they can't be HTML attributes.
  const list = document.getElementById('list');
  list.conversations = [
    { id: 'c1', title: 'Web component architecture',
      scope: { type: 'document' }, messageCount: 12,
      lastMessageAt: '2026-06-16T10:00:00Z', updatedAt: '2026-06-16T10:00:00Z' },
  ];
  list.activeId = 'c1';

  // Build a <kai-message> per turn — you control the thread array.
  const thread = document.getElementById('thread');
  let msgs = [
    { id: 'u1', role: 'user', parts: [{ type: 'text', text: 'Hello!' }] },
    { id: 'a1', role: 'assistant', parts: [{ type: 'text', text: 'Hi — how can I help?' }], actions: ['copy', 'like', 'dislike'] },
  ];

  const render = () => {
    thread.innerHTML = '';
    for (const m of msgs) {
      const el = document.createElement('kai-message');
      el.message = m;  // the full ChatMessage object
      thread.append(el);
    }
  };
  render();

  // Conversation switch
  list.addEventListener('kai-conversation-select', (e) => {
    list.activeId = e.detail.id;
    // load the thread for the selected conversation…
  });

  // Submit: append user turn, then stream the assistant reply
  const input = document.getElementById('input');
  input.addEventListener('kai-submit', async (e) => {
    const prompt = e.detail.value;
    const aId = crypto.randomUUID();

    msgs = [
      ...msgs,
      { id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: prompt }] },
      { id: aId, role: 'assistant', parts: [] },
    ];
    input.loading = true;
    render();

    let answer = '';
    for await (const token of streamFromYourModel(prompt)) {
      answer += token;
      msgs = msgs.map((m) =>
        m.id === aId ? { ...m, parts: [{ type: 'text', text: answer }] } : m
      );
      render();
    }
    input.loading = false;
  });
</script>
```

**Key points:**

- `kai-resizable` lays out panels horizontally (or vertically with `orientation="vertical"`). Each `kai-resizable-item` takes a `size`, `min`, and `max` — all as HTML attributes (scalar values).
- `kai-conversations` accepts a `conversations` property (the rows) and an optional `groups` property (the section headers, matched against each row's `groupId`). With no `groups`, the whole list renders in one Ungrouped section. Listen for `kai-conversation-select` to update `activeId`.
- Create one `<kai-message>` per turn and set its `message` property to the full `ChatMessage` object — `{ id, role, parts, actions?, avatar?, feedback? }`, where `parts` is an ordered array of `text` / `reasoning` / `tool` / `card` / `source` / `file` parts.
- `kai-prompt-input` fires `kai-submit` with `{ value, doc, entities, attachments }` — `value` is the flattened text, `doc` and `entities` the structured pills. Set `loading` to `true` while streaming and the send button disables; add `stoppable` to swap it for a Stop button that fires `kai-stop`.

## Adding a third panel

Insert another `<kai-resizable-item>` for anything — a `<kai-artifact>` preview, a canvas, or a debug inspector:

```html
<kai-resizable-item size="32%" min="240px">
  <kai-artifact id="preview" style="display:block;height:100%"></kai-artifact>
</kai-resizable-item>
```

Toggle it with the `hidden` boolean attribute/property; the remaining panels reflow automatically.

## Next steps

- **[Drop-in chat](/examples/drop-in-chat/)** — the 90% path: one `<kai-chat>` element with no manual wiring.
- **[Streaming recipe](/guides/recipes/streaming/)** — wire the `kai-submit` loop to a real SSE stream.
- **[`kai-resizable` reference](/components/resizable/)** — `orientation`, `maximizedIndex`, `kai-change`.
- **[`kai-conversations` reference](/components/conversations/)** — `groups` vs `conversations`, `activeId`, events.
- **[`kai-message` reference](/components/message/)** — `message` shape, `actionsReveal`, `proseSize`.
- **[`kai-prompt-input` reference](/components/prompt-input/)** — `loading`, `stoppable`, `triggers`, `kai-toolbar-action`.
