Skip to content
kitn AI/UI

Loading the elements

There are three ways to get the kai-* elements 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/elementsThe default. You use several elements, or you just want it to work. Works in SSR.
Per-element@kitn.ai/ui/elements/chatYou use only a couple of elements and your build tree-shakes.
Autoloader<script> (CDN / static)No build step — a CDN or static page. Loads each element on demand.
import '@kitn.ai/ui/elements';

One import, every kai-* element 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.

Import an element by its file basename, and your bundler includes only that element’s code:

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

The import path is the element’s filename in dist/elements/ — usually the tag minus the kai- prefix. You only need a row below for an element 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/elements/chat
<kai-code-block>@kitn.ai/ui/elements/code-block
<kai-message>@kitn.ai/ui/elements/message
<kai-prompt-input>@kitn.ai/ui/elements/prompt-input
<kai-markdown>@kitn.ai/ui/elements/markdown
<kai-reasoning>@kitn.ai/ui/elements/reasoning
<kai-attachments>@kitn.ai/ui/elements/attachments
<kai-artifact>@kitn.ai/ui/elements/artifact

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

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 element files and its on-demand imports 404. In a bundled app, use per-element imports instead.

<script type="module" src="https://cdn.jsdelivr.net/npm/@kitn.ai/ui/dist/elements/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 an element 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/elements/autoloader.js';
const chat = document.querySelector('kai-chat');
chat.messages = [{ id: '1', role: 'assistant', content: '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/elementsYes — side-effect only; no document on import
Per-element (@kitn.ai/ui/elements/chat)No — use inside a client-only boundary
AutoloaderNo — uses document; CDN <script>, not for SSR

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