Skip to content
kitn AI/UI

Composer

kai-composer

Lets a user reference a skill, an agent, or a plugin inline and hands the backend structured references.

  • Shadow DOM
  • Atomic entity pills
  • / and @ trigger menus
  • Keyword highlighting
  • Undo / redo
  • Structured doc model
  • Pre-populate via doc value

Register web components 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/web-components';
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 document value are arrays/objects, so assign them in JavaScript. 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? }) and the composer emits references, never resolved content; your backend expands /summarize or @code-reviewer before the model call.
  • text is a default flatten: each entity becomes its promptText ?? label. Pass your own serializer for a different wire format.
  • Per-kind decoration. Skills and agents render as sigil-led text (/summarize, @code-reviewer); plugins render as a richer chip, since a plugin bundles tools, skills, and agents. Recolor with --kai-pill-skill / --kai-pill-agent.

A ComposerDoc value seeds pills on mount and stays editable; kai-submit still emits the flattened text plus doc and entities.

highlights decorates matching words via the CSS Custom Highlight API. Decoration only: the text stays plain and editable, and highlights never enter the document model.

PropertyTypeDefaultNotes
theme'auto'Color mode (`auto` follows prefers-color-scheme).
value—Controlled value: a string or a full ComposerDoc (set as JS property).
placeholder—Placeholder text shown when the composer is empty.
disabledfalseDisable the composer entirely (non-interactive).
loadingfalseShow a loading/streaming state and block submit.
maxHeight240Maximum height in px before the content scrolls. Default 240.
submitOnEntertrueWhether 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—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.
EventDetailNotes
The composer lost focus.
An entity pill was inserted into the composer.
An entity pill was deleted from the composer.
The composer gained focus.
The user submitted (Enter or programmatic submit).
A trigger character was detected at the caret (e.g. `/` or `@`).
Record<string, never> The active trigger was dismissed (Escape, space, or outside click).
The content changed (fires on every input event).
MethodSignatureNotes
(options?: FocusOptions): voidFocus the editable element. `focus`/`blur` are NOT composed natively, so a host-level focus() can't reach the editable inside the shadow root. This is the only way to focus the composer programmatically.
(): voidBlur the editable element.
(): voidEmpty the composer to a blank doc (resets the internal value + history baseline; fires kai-value-change).
(): voidSubmit the current content programmatically, on the same path as Enter (fires kai-submit). Named `send`, not `submit`, to match the shared vocabulary and avoid any submit collision.
(entity: EntityRef): voidInsert an atomic entity pill (skill/agent/plugin) at the caret without typing a trigger (fires kai-entity-add).