Prompt Input Since v4.1.0
The chat composer, with a rich editor or a plain textarea as its input. The form lays out the input and its controls, and its markup decides between a single row and a two row composer.
Usage
The editor requires @tiptap/core, @tiptap/pm, @tiptap/extensions, @tiptap/extension-document, @tiptap/extension-paragraph, @tiptap/extension-text, @tiptap/extension-hard-break, @tiptap/extension-code, @tiptap/extension-code-block, @tiptap/extension-mention and @tiptap/suggestion. The textarea requires none.
import {
BuiPromptInput,
BuiPromptInputAttachments,
BuiPromptInputEditor,
BuiPromptInputFooter,
BuiPromptInputTextarea,
} from '@/bui/ui/prompt-input';<form buiPromptInput (ngSubmit)="send()">
<div buiPromptInputEditor placeholder="Ask anything" [(value)]="prompt"></div>
<button matIconButton class="primary" aria-label="Send">
<mat-icon svgIcon="corner-down-left" />
</button>
</form>Example
A single row composer under a thread. Enter sends, Shift+Enter adds a line.
<form buiPromptInput (ngSubmit)="send()">
<button matIconButton type="button" aria-label="Attach">
<mat-icon svgIcon="plus" />
</button>
<div buiPromptInputEditor placeholder="Ask anything" [(value)]="prompt"></div>
<button
matIconButton
class="primary"
aria-label="Send"
[disabled]="!prompt().trim()"
>
<mat-icon svgIcon="corner-down-left" />
</button>
<button matIconButton type="button" aria-label="Dictate">
<mat-icon svgIcon="mic" />
</button>
</form>Footer layout
buiPromptInputFooter takes a full row below the input, and the input then has the first line to itself. Every control goes in the footer, send included. Without a footer, the send button stays on the input line.
<form buiPromptInput>
<div
buiPromptInputEditor
class="min-h-[3lh]"
placeholder="Ask anything"
></div>
<div buiPromptInputFooter>
<button matButton type="button" class="tertiary icon-only" aria-label="Attach">
<mat-icon svgIcon="plus" />
</button>
<span class="flex-auto"></span>
<button matButton type="button" class="tertiary small">
<mat-icon svgIcon="shield-check" />
Ask before edits
<mat-icon iconPositionEnd svgIcon="chevron-down" />
</button>
<button matButton type="button" class="tertiary small">
Claude Sonnet 5.5
<mat-icon iconPositionEnd svgIcon="chevron-down" />
</button>
<button matButton class="primary icon-only" aria-label="Send">
<mat-icon svgIcon="corner-down-left" />
</button>
</div>
</form>Chin layout
A chin is a row of application markup under the form, such as a mode and a model selector. It is an alternative to the footer and not a Core part, so the composer itself stays a single row.
<div class="flex flex-col gap-y-1.5">
<form buiPromptInput>
<button matIconButton type="button" aria-label="Attach">
<mat-icon svgIcon="plus" />
</button>
<div buiPromptInputEditor placeholder="Ask anything"></div>
<button matIconButton class="primary" aria-label="Send">
<mat-icon svgIcon="corner-down-left" />
</button>
<button matIconButton type="button" aria-label="Dictate">
<mat-icon svgIcon="mic" />
</button>
</form>
<div class="flex items-center gap-x-1 px-2">
<button matButton type="button" class="tertiary small">
<mat-icon svgIcon="shield-check" />
Ask before edits
<mat-icon iconPositionEnd svgIcon="chevron-down" />
</button>
<button matButton type="button" class="tertiary small ms-auto">
Claude Sonnet 5.5
<mat-icon iconPositionEnd svgIcon="chevron-down" />
</button>
</div>
</div>Editor
The editor is the default input. Use the textarea when you want a native control without the Tiptap packages.
For anything beyond its inputs, the directive exposes the Tiptap instance as its editor property, listed in the API Reference.
Backticks make inline code, and triple backticks at the start of a line make a code block. Both keep their backticks in value, so the text reads as markdown.
References and commands
Typing @ suggests references: folders, files, chats or any other kind. A picked reference shows as @label and lands in value as @kind:id, so a backend can resolve it. Typing / suggests commands. A picked command shows as /label and lands in value as /id. No event fires; the host acts on the submitted text.
While the suggestions are open, the arrow keys, Enter and Tab drive them and Escape closes them. The optional icon on each item is an svg icon name.
<form buiPromptInput>
<div
buiPromptInputEditor
placeholder="Type @ to reference, / for commands"
[references]="references"
[commands]="commands"
[(value)]="prompt"
></div>
<button matIconButton class="primary" aria-label="Send">
<mat-icon svgIcon="corner-down-left" />
</button>
</form>import { PromptCommand, PromptReference } from '@/bui/ui/prompt-input';
readonly references: PromptReference[] = [
{ id: 'src/app', label: 'src/app', kind: 'folder', icon: 'folder' },
{ id: 'src/app/app.routes.ts', label: 'app.routes.ts', kind: 'file', icon: 'file' },
{ id: 'chat-1', label: 'Pricing page redesign', kind: 'chat', icon: 'message-square' },
];
readonly commands: PromptCommand[] = [
{ id: 'context', label: 'context', icon: 'chart-pie' },
{ id: 'clear', label: 'clear', icon: 'eraser' },
{ id: 'model', label: 'model', icon: 'cpu' },
];Textarea
buiPromptInputTextarea is a native control with no dependencies. It grows with its content up to ten lines and binds like any textarea.
<form buiPromptInput (ngSubmit)="send()">
<button matIconButton type="button" aria-label="Attach">
<mat-icon svgIcon="plus" />
</button>
<textarea
buiPromptInputTextarea
name="prompt"
placeholder="Ask anything"
[(ngModel)]="prompt"
></textarea>
<button
matIconButton
class="primary"
aria-label="Send"
[disabled]="!prompt().trim()"
>
<mat-icon svgIcon="corner-down-left" />
</button>
</form>Submit handling
Both inputs submit the enclosing form on Enter and add a line on Shift+Enter. In the editor, Enter submits inside a code block too, and Shift+Enter adds a line there. In the textarea, an Enter that confirms an IME candidate does not submit.
Enter submits whether or not the send button is disabled, so the submit handler skips empty prompts. The send button is the form's submit button; every other button in the form takes type="button". Reset the value after sending to clear the input.
<form buiPromptInput (ngSubmit)="send()">
<div buiPromptInputEditor [(value)]="prompt"></div>
<button matIconButton class="primary" aria-label="Send">
<mat-icon svgIcon="corner-down-left" />
</button>
</form>readonly prompt = signal('');
send() {
if (!this.prompt().trim()) return;
// Send this.prompt() to your backend
this.prompt.set('');
}API Reference
buiPromptInput
The form. Lays out its children in a row and sizes their icon buttons. Clicking its padding focuses the input.
buiPromptInputAttachments
A full row above the input for attachments. Optional.
buiPromptInputEditor
The rich editor input, with references, commands and inline code.
| Input | Type | Default | Description |
|---|---|---|---|
placeholder | string | '' | Text shown while the editor is empty. |
references | PromptReference[] | [] | Items suggested after typing @. |
commands | PromptCommand[] | [] | Items suggested after typing /. |
value | model<string> | '' | Two-way binding for the plain text. Writing it replaces the editor content. |
| Property | Type | Description |
|---|---|---|
editor | Editor | undefined | The Tiptap editor instance. |
buiPromptInputTextarea
The native textarea input. Grows with its content.
buiPromptInputFooter
A full row below the input for tools, selectors and the send button. Optional.