Skip to content
kitn AI/UI

Prompt Input

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

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 propssuggestions, triggers, kindIcons, and attachments are arrays/objects, so set them in JavaScript rather than as HTML attributes.
  • Scalar flagsplaceholder, disabled, loading, search, voice, stoppable, and suggestionMode work as plain attributes or properties.
  • Loading + stoppable — set loading while a response streams; add stoppable to swap the send button for a Stop button that fires kai-stop.
  • Custom toolbar buttons — place <kai-action id icon tooltip> children inside the element; clicks fire kai-toolbar-action with detail.action.
  • Entity pills — set triggers to let / and @ insert atomic skill/agent/plugin pills. kai-submit and kai-value-change then carry the structured doc + entities alongside the flattened value string. See <kai-composer> for the full pill model.
  • Pre-populate pills — set value to 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 elementAttributesText contentNotes
actioniconidlabeltooltip
Yes

Out-of-the-box composer with starter suggestions. Type and press Enter (or click a chip) to fire kai-submit.

search and voice flags add Globe and Mic buttons to the toolbar, firing kai-search and kai-voice respectively.

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.

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.

Set attachments to pre-populate staged files. The element manages attachment state — the paperclip adds more, each chip has a remove button.

loading blocks submit while a response streams. stoppable swaps send for a Stop button that fires kai-stop.

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.

SlotModePurpose
injectInside 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.
injectLeading controls in the input toolbar, where a + menu goes.
injectTrailing controls in the toolbar, before the Send button.

Restyle the send button via ::part(send), or hide it for an Enter-only composer.

PartPurposeExample
The send button. Restyle from outside, or hide it entirely (Enter-only). Hiding is pure CSS, which is why there is no `submit="never"`.
kai-prompt-input::part(send) { display: none } /* Enter-only; or restyle: background, border-radius, … */
PropertyTypeDefaultNotes
theme'auto'Color mode (`auto` follows prefers-color-scheme).
valueValue 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'Send a message...'Placeholder text shown in the empty input.
disabledfalseDisable the input and submit button entirely (non-interactive).
loadingfalseShow the loading/streaming state and block submit (use while awaiting a reply).
suggestionsStarter prompts shown above the input. Clicking one follows `suggestionMode`. Set as a JS property.
suggestionMode'submit'What clicking a suggestion does: `'submit'` (default) sends it immediately as if typed and submitted; `'fill'` just places it in the input.
searchfalseShow a Search (Globe) button in the left toolbar; clicking it fires a `search` event.
voicefalseShow a Voice (Mic) button in the left toolbar; clicking it fires a `voice` event.
stoppablefalseWhen 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'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.
attachtrueWhen `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`.
attachmentsAttachments 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).
triggersRich 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.
kindIconsDefault 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.
EventDetailNotes
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).
Record<string, never> The Search (Globe) toolbar button was clicked.
Record<string, never> The Stop button was clicked while `stoppable` and `loading` are both true.
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.
A suggestion was clicked while `suggestion-mode="fill"`.
A custom `<kai-action>` toolbar button was clicked. `action` is the `id` of the `<kai-action>` element that was clicked.
The input changed (fires on every edit). Carries the flattened `value` plus the structured `doc` + `entities`.
Record<string, never> The Voice (Mic) toolbar button was clicked.
MethodSignatureNotes
(options?: FocusOptions): voidFocus the text editor inside the shadow root (not the hidden file input).
(): voidBlur the focused input control.
(): voidClear the text and any staged attachments (fires kai-value-change / kai-attachments-change so a controlled consumer can react).
(): voidSend 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.