# HTML

Use AI/UI web components in plain HTML and vanilla JavaScript — no framework, no build tool required.

Drop `<kai-*>` web components into any HTML page with a single import. No framework, no build configuration — the elements register themselves globally and work in every modern browser.

## Install

```bash
npm install @kitn.ai/ui
```

Register all web components once with a side-effect import near your app entry:

```js

```

Every `<kai-*>` tag is then available globally — drop them anywhere in your HTML.

> **tip:** 
Skip npm entirely and load from a CDN. For quick prototypes this works immediately; for production, pin a specific version to avoid breaking changes (the package is pre-1.0).

```html
<script type="module">

</script>
```

## The property / event contract

Every `kai-*` web component follows the same two rules:

- **Rich data (arrays, objects) → JS properties.** Set them in a `<script>` block, not as HTML attributes. Attributes only work for scalars (strings, booleans, numbers).
- **Interactions → `addEventListener`.** All events are `CustomEvent`s dispatched on the element itself. They do not bubble.

```js
// Arrays and objects — set the property in JavaScript, not as an attribute
el.messages = [{ id: '1', role: 'assistant', parts: [{ type: 'text', text: 'Hi' }] }];

// Scalars — fine as attributes or properties
el.setAttribute('theme', 'dark');

// Events — always addEventListener; they do not bubble
el.addEventListener('kai-submit', (e) => console.log(e.detail.value));
```

**Reactivity:** always assign a **new array** (and a new object for any message you change). Mutating an existing array or object in place will not trigger a re-render.

## Complete example — `<kai-chat>`

`<kai-chat>` is the batteries-included shell: give it a `messages` array, handle the `kai-submit` event, and stream your model's reply back. You own the request; the element owns the UI.

```html
<!DOCTYPE html>
<html>
<head>
  <style>
    html, body { margin: 0; height: 100%; }
    .app { display: flex; flex-direction: column; height: 100dvh; }
  </style>
</head>
<body>
  <div class="app">
    <kai-chat id="chat" style="flex: 1; min-height: 0;"></kai-chat>
  </div>

  <script type="module">

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

    const chat = document.getElementById('chat');

    // Seed an initial assistant message
    chat.messages = [
      {
        id: crypto.randomUUID(),
        role: 'assistant',
        parts: [{ type: 'text', text: 'Hello! How can I help?' }],
        actions: ['copy', 'like', 'dislike'],
      },
    ];
    chat.suggestions = ['Summarize the chat', 'Start fresh'];

    chat.addEventListener('kai-submit', async (e) => {
      const text = e.detail.value;

      // Append the user message — always assign a new array
      const history = [
        ...chat.messages,
        { id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text }] },
      ];
      chat.messages = history;
      chat.loading = true;

      // Create an empty assistant placeholder to stream into
      const aid = crypto.randomUUID();
      chat.messages = [...history, { id: aid, role: 'assistant', parts: [] }];

      let answer = '';
      for await (const token of streamFromYourAPI(history)) {
        answer += token;
        // Replace only the placeholder — every other message stays the same
        chat.messages = chat.messages.map((m) =>
          m.id === aid ? { ...m, parts: [{ type: 'text', text: answer }] } : m,
        );
      }

      chat.loading = false;
    });
  </script>
</body>
</html>
```

## Compose individual elements

`<kai-chat>` is one option, not the only one. Every element can be placed independently. Here's a sidebar + thread layout using `<kai-conversations>` and `<kai-chat>`:

```html
<style>
  html, body { margin: 0; height: 100%; }
  .workspace { display: flex; height: 100dvh; }
</style>

<div class="workspace">
  <kai-conversations id="sidebar" style="width: 280px; flex-shrink: 0;"></kai-conversations>
  <kai-chat id="chat" style="flex: 1; min-width: 0;"></kai-chat>
</div>

<script type="module">

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

  const sidebar = document.getElementById('sidebar');
  const chat = document.getElementById('chat');

  sidebar.conversations = myConversations;
  chat.messages = loadMessages(myConversations[0]?.id);

  sidebar.addEventListener('kai-conversation-select', (e) => {
    chat.messages = loadMessages(e.detail.id);
  });

  sidebar.addEventListener('kai-new-chat', () => startNewConversation());

  chat.addEventListener('kai-submit', (e) => sendMessage(e.detail.value));
</script>
```

### Add a resizable divider

Wrap panels in `<kai-resizable>` with one `<kai-resizable-item>` each — drag handles are inserted automatically. Each item accepts a `size` (px or `%`) and optional `min`/`max`. Listen for `kai-change` to persist the layout.

```html
<style>
  html, body { margin: 0; height: 100%; }
  .app { display: flex; flex-direction: column; height: 100dvh; }
</style>

<div class="app">
  <kai-resizable orientation="horizontal" style="flex: 1; min-height: 0;">
    <kai-resizable-item size="25%" min="200px">
      <kai-conversations id="sidebar"></kai-conversations>
    </kai-resizable-item>
    <kai-resizable-item>
      <kai-chat id="chat"></kai-chat>
    </kai-resizable-item>
  </kai-resizable>
</div>

<script type="module">

  const resizable = document.querySelector('kai-resizable');
  resizable.addEventListener('kai-change', (e) => {
    localStorage.setItem('panel-sizes', JSON.stringify(e.detail.sizes));
  });
</script>
```

## Standalone display elements

You can drop any single element into an existing page without adopting the full chat shell. `<kai-markdown>`, `<kai-code-block>`, and `<kai-artifact>` are common standalone picks for rendering AI-generated content:

```html
<script type="module">

</script>

<kai-markdown content="## Result\n\nHere is your **summary**."></kai-markdown>
<kai-code-block code="const greet = (name) => `Hello, ${name}!`;" language="js"></kai-code-block>
```

Each element fills its container and is controlled entirely through properties and events.

## Key events reference

| Element | Event | `detail` |
|---|---|---|
| `kai-chat` | `kai-submit` | `{ value: string }` |
| `kai-chat` | `kai-model-change` | `{ model: string }` |
| `kai-conversations` | `kai-conversation-select` | `{ id: string }` |
| `kai-conversations` | `kai-new-chat` | — |
| `kai-conversations` | `kai-toggle-sidebar` | — |
| `kai-resizable` | `kai-change` | `{ sizes: string[] }` |

For the full API of any element — all properties, events, and their types — see the individual component page in the [Components](/components/attachments/) reference.

## Theming

By default each element is styled inside its own Shadow DOM and needs no external CSS. To override design tokens (colors, radii, spacing), load `theme.css`:

```html
<!-- bundler -->
<link rel="stylesheet" href="./node_modules/@kitn.ai/ui/theme.css" />

<!-- CDN -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@kitn.ai/ui/theme.css" />
```

Then override any `--color-*` or `--radius-*` custom properties on `:root` or a scoping selector.
