kai-prompt-input
A Shadow DOM chat composer — an auto-growing rich input, send button, suggestion chips, attachments, and an extensible toolbar. It supports inline entity pills (skills, agents, plugins) and drops into any framework without style conflicts.
- Shadow DOM
- Uncontrolled by default
- Starter suggestions
- Entity pills (/ skills, @ agents)
- Custom toolbar buttons
- Pre-seeded attachments
Preview
Section titled “Preview”Register elements once, then wire up JS properties and DOM events:
<kai-prompt-input id="input" style="display:block; width:100%;"></kai-prompt-input>
<script type="module"> import '@kitn.ai/ui/elements';
await customElements.whenDefined('kai-prompt-input');
const input = document.getElementById('input');
input.suggestions = ['Summarize this thread', 'Draft a reply']; // `/` inserts skill pills, `@` opens an agent/plugin menu. input.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' }] }, ];
input.addEventListener('kai-submit', (e) => console.log('send:', e.detail.value, e.detail.entities, e.detail.attachments)); input.addEventListener('kai-value-change', (e) => console.log('typing:', e.detail.value));</script>- Array / object props —
suggestions,triggers,kindIcons, andattachmentsare arrays/objects, so set them in JavaScript rather than as HTML attributes. - Scalar flags —
placeholder,disabled,loading,search,voice,stoppable, andsuggestionModework as plain attributes or properties. - Loading + stoppable — set
loadingwhile a response streams; addstoppableto swap the send button for a Stop button that fireskai-stop. - Custom toolbar buttons — place
<kai-action id icon tooltip>children inside the element; clicks firekai-toolbar-actionwithdetail.action. - Entity pills — set
triggersto let/and@insert atomic skill/agent/plugin pills.kai-submitandkai-value-changethen carry the structureddoc+entitiesalongside the flattenedvaluestring. See<kai-composer>for the full pill model. - Pre-populate pills — set
valueto an array of segments ({ type: 'text', text }and{ type: 'entity', entity }) instead of a string to seed pills programmatically; the user edits from there. The prop tables call that shape a ComposerDoc; it is a structural type, not something you import.
| Child element | Attributes | Text content | Notes |
|---|---|---|---|
| <kai-action> | actioniconidlabeltooltip | Yes | — |
Examples
Section titled “Examples”Default
Section titled “Default”Out-of-the-box composer with starter suggestions. Type and press Enter (or click a chip) to fire kai-submit.
With Voice and Search
Section titled “With Voice and Search”search and voice flags add Globe and Mic buttons to the toolbar, firing kai-search and kai-voice respectively.
Entity Pills
Section titled “Entity Pills”Set triggers so / inserts a skill and @ opens a sectioned menu of agents and plugins. Each selection becomes an atomic pill; Backspace deletes one whole. kai-submit carries the structured doc + entities.
Pre-Populated Pills
Section titled “Pre-Populated Pills”Set value to a ComposerDoc to seed pills programmatically — handy for “edit this message” or templated drafts. The pills render on mount and stay editable; submit still emits the flattened value plus the structured doc + entities.
Pre-Seeded Attachments
Section titled “Pre-Seeded Attachments”Set attachments to pre-populate staged files. The element manages attachment state — the paperclip adds more, each chip has a remove button.
Loading State
Section titled “Loading State”loading blocks submit while a response streams. stoppable swaps send for a Stop button that fires kai-stop.
Disabled
Section titled “Disabled”disabled makes the composer entirely non-interactive — useful for read-only or locked states.
Project controls into the input’s shadow: a status strip above the textarea, or leading/trailing toolbar clusters — where a + menu or a model switcher goes.
| Slot | Mode | Purpose |
|---|---|---|
| input-top | inject | Inside the card, above the textarea (e.g. an inline status strip). For content above/below the whole card, use your own layout; that is light DOM you control. |
| toolbar-start | inject | Leading controls in the input toolbar, where a + menu goes. |
| toolbar-end | inject | Trailing controls in the toolbar, before the Send button. |
Styling
Section titled “Styling”Restyle the send button via ::part(send), or hide it for an Enter-only composer.
| Part | Purpose | Example |
|---|---|---|
| send | The send button. Restyle from outside, or hide it entirely (Enter-only). Hiding is pure CSS, which is why there is no `submit="never"`. | |
| Property | Type | Default | Notes |
|---|---|---|---|
| theme | "light" | "dark" | "auto" | 'auto' | Color mode (`auto` follows prefers-color-scheme). |
| value | — | Value of the input, as a JS property. A **string** is the controlled text mirror (the host owns it and updates on `kai-value-change`). A **ComposerDoc** (array of text/entity segments) is a one-time **seed** that pre-populates pills (skills/agents/plugins); the user then edits freely. Leave unset for uncontrolled behavior. `kai-submit`/`kai-value-change` always emit `value` as the flattened string (back-compat) plus the structured `doc` + `entities`. | |
| placeholder | string | 'Send a message...' | Placeholder text shown in the empty input. |
| disabled | boolean | false | Disable the input and submit button entirely (non-interactive). |
| loading | boolean | false | Show the loading/streaming state and block submit (use while awaiting a reply). |
| suggestions | string[] | — | Starter prompts shown above the input. Clicking one follows `suggestionMode`. Set as a JS property. |
| suggestionMode | "submit" | "fill" | 'submit' | What clicking a suggestion does: `'submit'` (default) sends it immediately as if typed and submitted; `'fill'` just places it in the input. |
| search | boolean | false | Show a Search (Globe) button in the left toolbar; clicking it fires a `search` event. |
| voice | boolean | false | Show a Voice (Mic) button in the left toolbar; clicking it fires a `voice` event. |
| stoppable | boolean | false | When set and `loading` is true, the send button is replaced by a Stop button (square icon, "Stop" aria-label). Clicking it fires `kai-stop`. |
| submit | "always" | "auto" | 'always' | Send-button visibility. `'always'` (default) always shows it; `'auto'` shows it only when there's text/attachments (an empty composer hides it, though Enter still submits). To hide it entirely (Enter-only), it's pure CSS: `::part(send){display:none}`, no prop needed. Restyle via `::part(send)`. The Stop button (`stoppable` + `loading`) is unaffected. |
| attach | boolean | true | When `false`, hides the built-in paperclip attach button even though the element otherwise supports attachments. Use this when a `+` menu in `toolbar-start` already exposes "Add files", to avoid a duplicate control. Defaults to `true`. |
| attachments | — | Attachments to seed the input with (so a consumer can pre-populate staged files without an upload). Set as a JS property; the element then manages its own attachment state from there (add via the paperclip, remove per chip). | |
| triggers | — | Rich entity triggers. Each `{ char, kind, items }` opens a caret-anchored menu that inserts an atomic pill. Convention: `/` → skills, `@` → agents (plugins are the grouping/provenance of those items). Set as a JS property. | |
| kindIcons | Record<string, string> | — | Default icon per entity kind (kind → image URL/data-URI) for pills/menu items without their own `icon`. Overrides the built-in agent/plugin glyphs. JS property. |
Events
Section titled “Events”| Event | Detail | Notes |
|---|---|---|
| kai-attachments-change | | The staged attachments changed: a file was added (via the paperclip) or removed (per-chip ×). Carries the full current list so a consumer can react in real time (validate, show upload progress, toggle the send button). |
| kai-search | Record<string, never> | The Search (Globe) toolbar button was clicked. |
| kai-stop | Record<string, never> | The Stop button was clicked while `stoppable` and `loading` are both true. |
| kai-submit | | The user submitted the prompt (Enter or send button). `value` is the flattened text (back-compat); `doc` is the structured document and `entities` the inserted pills (skills/agents) for downstream expansion. |
| kai-suggestion-click | | A suggestion was clicked while `suggestion-mode="fill"`. |
| kai-toolbar-action | | A custom `<kai-action>` toolbar button was clicked. `action` is the `id` of the `<kai-action>` element that was clicked. |
| kai-value-change | | The input changed (fires on every edit). Carries the flattened `value` plus the structured `doc` + `entities`. |
| kai-voice | Record<string, never> | The Voice (Mic) toolbar button was clicked. |
Methods
Section titled “Methods”| Method | Signature | Notes |
|---|---|---|
| focus | (options?: FocusOptions): void | Focus the text editor inside the shadow root (not the hidden file input). |
| blur | (): void | Blur the focused input control. |
| clear | (): void | Clear the text and any staged attachments (fires kai-value-change / kai-attachments-change so a controlled consumer can react). |
| send | (): void | Send the current value programmatically, on the same path as Enter / the send button (fires kai-submit, then clears staged attachments). Named `send`, not `submit`, to avoid colliding with the `submit` prop. |