Skip to content
kitn AI/UI

Loading the web components

There are three ways to get the kai-* web components onto a page. They render the same components — the difference is how their code reaches the browser. Most apps want the first one.

StrategyImportReach for it when
Register all@kitn.ai/ui/web-componentsThe default. You use several web components, or you just want it to work. Works in SSR.
Per-web-component@kitn.ai/ui/web-components/chatYou use only a couple of web components and your build tree-shakes.
Autoloader<script> (CDN / static)No build step and a handful of web components. Loads each on demand.
import '@kitn.ai/ui/web-components';

One import, every kai-* web component registered. The simplest setup, and the SSR-safe entry point — the import is side-effect-only and doesn’t touch document until the browser runs it, so it won’t throw in a server render.

The same bundle works with no build at all — a single self-contained fetch from a CDN:

<script type="module">
import 'https://cdn.jsdelivr.net/npm/@kitn.ai/ui/dist/kai.es.js';
</script>

Import a web component by its file basename, and your bundler includes only that web component’s code:

import '@kitn.ai/ui/web-components/chat';
import '@kitn.ai/ui/web-components/code-block';

The import path is the web component’s filename in dist/web-components/ — usually the tag minus the kai- prefix. You only need a row below for a web component you place in your markup yourself; the message / markdown / reasoning / input parts of a chat are already inside kai-chat:

ElementImport
<kai-chat>@kitn.ai/ui/web-components/chat
<kai-code-block>@kitn.ai/ui/web-components/code-block
<kai-message>@kitn.ai/ui/web-components/message
<kai-prompt-input>@kitn.ai/ui/web-components/prompt-input
<kai-markdown>@kitn.ai/ui/web-components/markdown
<kai-reasoning>@kitn.ai/ui/web-components/reasoning
<kai-attachments>@kitn.ai/ui/web-components/attachments
<kai-artifact>@kitn.ai/ui/web-components/artifact

The autoloader watches the DOM and registers each web component on demand — only the tags present on a given page are ever loaded.

That on-demand loading is paid in requests: each web component’s module and its dependencies arrive as separate fetches. On a page using a few web components — a <kai-markdown> here, a <kai-code-block> there — that’s the cheapest path by far.

A <kai-chat> page is close to its worst case: the chat transitively pulls most of the kit, so the autoloader downloads nearly the same bytes as the register-all bundle across many more requests. For a no-build chat page, load dist/kai.es.js instead — one fetch, everything registered.

It is a CDN / static-file tool: load it from a <script type="module"> tag. It is not importable through a bundler — Vite and webpack relocate it away from the web-component files and its on-demand imports 404. In a bundled app, use per-web-component imports instead.

<script type="module" src="https://cdn.jsdelivr.net/npm/@kitn.ai/ui/dist/web-components/autoloader.js"></script>

Here it is running live. This iframe loads only the autoloader — watch the panel for the modules it fetches on demand, then add a web component and see it load on the fly:

On import it scans for undefined kai-* elements and starts a MutationObserver. Any kai-* element added to the DOM later — including ones injected by client-side routing — is registered the moment it appears.

<!-- Only kai-chat loads. The others register only if you add them. -->
<kai-chat style="display:block; height:100dvh;"></kai-chat>
<script type="module">
import 'https://cdn.jsdelivr.net/npm/@kitn.ai/ui/dist/web-components/autoloader.js';
const chat = document.querySelector('kai-chat');
chat.messages = [{ id: '1', role: 'assistant', parts: [{ type: 'text', text: 'Hello!' }] }];
chat.addEventListener('kai-submit', (e) => console.log(e.detail.value));
</script>

Syntax highlighting (Shiki) loads on demand — per language, the first time a code block with that language renders, with no WebAssembly. If you never render a code block, it never loads. This holds for all three strategies above.

StrategySSR-safe?
@kitn.ai/ui/web-componentsYes — registration is gated on window; no document on import
Per-web-component (@kitn.ai/ui/web-components/chat)No — throws window is not defined; use inside a client-only boundary
AutoloaderImports without throwing, but never use it here — it must resolve relative to its own URL, so it is a CDN <script>, not a bundled import

For SSR meta-frameworks, see the meta-frameworks guide for framework-specific client boundary patterns.