kai-composer
A rich text input that behaves like a textarea but carries atomic entity pills — skills, agents, plugins — inserted from / and @ menus. It emits a structured { doc, text, entities } for your backend to expand, and you set its value as a string or a full document.
- Shadow DOM
- Atomic entity pills
- / and @ trigger menus
- Keyword highlighting
- Undo / redo
- Structured doc model
- Pre-populate via doc value
Preview
Section titled “Preview”Register elements once, then set the trigger definitions as a JS property and listen for events:
<kai-composer id="composer" style="display:block; width:100%;"></kai-composer>
<script type="module"> import '@kitn.ai/ui/elements';
await customElements.whenDefined('kai-composer'); const composer = document.getElementById('composer');
// `/` inserts skills; `@` opens a sectioned menu of agents + plugins. composer.triggers = [ { char: '/', kind: 'skill', items: [ { id: 'summarize', label: 'Summarize', description: 'Summarize the thread' }, ]}, { char: '@', kind: 'agent', items: [ { id: 'code-reviewer', label: 'Code Reviewer', group: 'Agents' }, { id: 'documents', label: 'Documents', kind: 'plugin', group: 'Plugins' }, ]}, ];
composer.addEventListener('kai-submit', (e) => { // e.detail = { doc, text, entities } — references, not resolved content. console.log(e.detail.text, e.detail.entities); });</script>- Set objects as properties —
triggers,highlights,kindIcons, and a documentvalueare arrays/objects, so assign them in JavaScript, never as HTML attributes. Scalars (placeholder,disabled,loading,maxHeight,submitOnEnter) work as attributes. - The document is the source of truth. Each pill is an
EntityRef—{ kind, id, label, icon?, promptText?, data? }. The composer emits references, never resolved content; your backend expands/summarizeor@code-reviewerbefore the model call, exactly like Claude Code and OpenCode do client-side. textis a default flatten — each entity becomes itspromptText ?? label. Pass your own serializer if you need a different wire format.- Per-kind decoration. Skills and agents render as light, sigil-led text (
/summarize,@code-reviewer); plugins render as a richer chip, since a plugin bundles tools/skills/agents. Override the colors with the--kai-pill-skill/--kai-pill-agentCSS variables.
Examples
Section titled “Examples”Entity Pills
Section titled “Entity Pills”Set triggers to enable the menus. Type / for a skill or @ for an agent or plugin; each selection inserts an atomic pill you can delete whole with Backspace. Arrow onto a pill to select it as one unit.
Pre-Populated Pills
Section titled “Pre-Populated Pills”Set value to a ComposerDoc — an array of text and entity segments — to seed pills programmatically. They render on mount and stay fully editable. kai-submit still emits the flattened text plus the structured doc and entities.
Keyword Highlighting
Section titled “Keyword Highlighting”Set highlights to decorate matching words via the CSS Custom Highlight API. This is decoration only — the text stays plain and editable, and highlights never enter the document model.
| Property | Type | Default | Notes |
|---|---|---|---|
| value | — | Controlled value — string or a full ComposerDoc (set as JS property). | |
| placeholder | string | — | Placeholder text shown when the composer is empty. |
| disabled | boolean | false | Disable the composer entirely (non-interactive). |
| loading | boolean | false | Show a loading/streaming state and block submit. |
| maxHeight | string | number | 240 | Maximum height in px before the content scrolls. Default 240. |
| submitOnEnter | boolean | true | Whether pressing Enter (without Shift) submits. Default true. |
| triggers | — | Trigger definitions — set as a JS property. | |
| highlights | — | Keyword highlight rules — set as a JS property. | |
| kindIcons | Record<string, string> | — | Default icon per entity kind (kind → image URL/data-URI) for items without their own `icon`. Overrides the built-in agent/plugin glyphs. JS property. |
Events
Section titled “Events”| Event | Detail | Notes |
|---|---|---|
| kai-blur | | The composer lost focus. |
| kai-entity-add | | An entity pill was inserted into the composer. |
| kai-entity-remove | | An entity pill was deleted from the composer. |
| kai-focus | | The composer gained focus. `focus`/`blur` are NOT composed natively, so they don't escape the shadow root — these re-expose them on the host. (For `keydown`/`paste`/`focusin`/`focusout`, listen NATIVELY on `<kai-composer>`: they're composed and already cross the shadow boundary.) |
| kai-submit | | The user submitted the composer (Enter or programmatic submit). |
| kai-trigger | | A trigger character was detected at the caret (e.g. `/` or `@`). |
| kai-trigger-close | Record<string, never> | The active trigger was dismissed (Escape, space, or outside click). |
| kai-value-change | | The content changed (fires on every input event). |