PromptInput

pro

PromptInput combines autosizing message entry, attachments, command selection, and explicit leading and trailing toolbars.

Installation

Add this component with the NateUI CLI.

npx nateui@latest add PromptInput

Examples

Attachments

Mentions & Commands

Busy And Stop

Thinking...

Extension Above Card

I want to make some changes

API

PromptInput

PropDescriptionTypeDefault
contentModeSelects the text model or structured-parts model. The two modes have mutually exclusive state props.'text' | 'parts''text'
onSubmitRequired. Receives trimmed readable text and current attachments. In parts mode, the optional third argument contains the original parts plus reference ranges measured against the trimmed emitted text.(value: string, attachments: AttachmentData[], details?: PromptInputSubmissionDetails) => void—
layoutUses a stacked composer or a single-row compact start state.'stacked' | 'compact''stacked'
attachmentsControlled attachment records rendered above the field.AttachmentData[]—
onAttachmentsChangeReceives selected and removed attachment records.(attachments: AttachmentData[]) => void—
attachmentAcceptNative file-input accept filter.string—
commandsCommand records filtered when the caret is in a / token. Records without an outcome preserve legacy insertion (value ?? id) and callback behavior; kind: 'command' creates a structured command part.PromptInputCommand[][]
suggestionsShared-menu records filtered by their @ or / trigger.PromptInputSuggestion[][]
emptyStateContent announced when a configured trigger group has no filtered matches.ReactNode'No matching options'
onCommandSelectCalled after a command is selected. Legacy commands insert their slash token first; commands with an outcome apply that outcome.(command: PromptInputCommand) => void—
onSuggestionSelectCalled after a suggestion is selected, including callback-only action outcomes. Selection does not submit the message.(suggestion: PromptInputSuggestion) => void—
renderReferenceIndicatorRenders a decorative indicator over the reserved leading @ glyph in parts mode. Returning null or undefined keeps the visible @ fallback.(reference: PromptInputReference) => ReactNode—
onStopCalled by PromptInput.Submit while status="busy".() => void—
statusSwitches the submit control between send and stop.PromptInputStatus'idle'
valueControlled string-mode message value. Pair with onChange; unavailable in parts mode.string—
onChangeReceives each string-mode value change; unavailable in parts mode.(value: string) => void—
partsControlled structured content. Pair with onPartsChange; unavailable in text mode.PromptInputPart[]—
defaultPartsInitial uncontrolled structured content; available only with contentMode="parts".PromptInputPart[][{ type: 'text', text: '' }]
onPartsChangeReceives each structured-parts change; unavailable in text mode.(parts: PromptInputPart[]) => void—
placeholderText shown when the message field is empty.string'Message…'
refExposes clear() and focus() methods.Ref<PromptInputRef>—
...formPropsStandard <form> attributes forwarded to the root.ComponentProps<'form'>—

String mode is the default and accepts value/onChange. Structured mode requires contentMode="parts" and accepts parts/onPartsChange or defaultParts; it rejects value/onChange so the same text is not stored twice. details.parts retains text, reference and command parts, including surrounding whitespace. Command parts carry their stable id and display label; the readable projection uses /${id}. details.references contains reference occurrences within the trimmed submitted string, with UTF-16 start and end offsets.

renderReferenceIndicator affects only the aria-hidden visual overlay; reference parts, textarea text, caret positions, and submitted offsets continue to use the literal @label projection. The callback result is presentation and is never stored in the serializable parts. The native @ glyph keeps its original inline width beneath the indicator. Highlighted command tokens retain the readable literal /id; command icons remain in the menu.

PromptInput.Toolbar

Use one ToolbarStart and one ToolbarEnd inside each Toolbar. The end group contains PromptInput.Submit explicitly.

PartDescription
ExtensionBounded bar above the card (queue row, context strip). Defaults carry the boundary; className overrides via tailwind-merge.
ToolbarStartLeading actions, such as AttachButton or a caller-owned picker.
ToolbarEndTrailing controls and Submit.
AttachButtonOpens the native multi-file picker owned by the root composer.
SubmitSends the current text/attachments, or stops while busy.

Types

import type { ReactNode } from 'react';
 
type AttachmentData = {
  id: string;
  mediaType: string;
  name: string;
  url: string;
};
 
type PromptInputLayout = 'stacked' | 'compact';
type PromptInputStatus = 'idle' | 'busy';
 
type PromptInputReference = {
  type: string;
  id: string;
  label: string;
};
 
type PromptInputTextPart = { type: 'text'; text: string };
type PromptInputReferencePart = {
  type: 'reference';
  occurrenceId: string;
  reference: PromptInputReference;
};
type PromptInputCommandPart = {
  type: 'command';
  occurrenceId: string;
  command: { id: string; label: string };
};
type PromptInputPart = PromptInputTextPart | PromptInputReferencePart | PromptInputCommandPart;
 
type PromptInputReferenceOccurrence = PromptInputReference & {
  occurrenceId: string;
  start: number;
  end: number;
};
 
type PromptInputSubmissionDetails = {
  parts: PromptInputPart[];
  references: PromptInputReferenceOccurrence[];
};
 
type PromptInputChoice = { label: string; value: string };
type PromptInputSelectionOutcome =
  | { kind: 'action'; actionId: string }
  | {
      kind: 'text';
      insertText: string;
      argumentHint?: string;
      choices?: PromptInputChoice[];
    }
  | { kind: 'command'; argumentHint?: string; choices?: PromptInputChoice[] }
  | { kind: 'reference'; reference: PromptInputReference };
 
type PromptInputCommand = {
  id: string;
  label: string;
  description?: ReactNode;
  icon?: ReactNode;
  group?: string;
  keywords?: string[];
  value?: string;
  outcome?: PromptInputSelectionOutcome;
};
 
type PromptInputSuggestion = {
  id: string;
  label: string;
  trigger: '@' | '/';
  outcome: PromptInputSelectionOutcome;
  description?: ReactNode;
  icon?: ReactNode;
  group?: string;
  keywords?: string[];
};
 
type PromptInputTextProps = {
  contentMode?: 'text';
  value?: string;
  onChange?: (value: string) => void;
  parts?: never;
  defaultParts?: never;
  onPartsChange?: never;
};
 
type PromptInputPartsProps = {
  contentMode: 'parts';
  parts?: PromptInputPart[];
  defaultParts?: PromptInputPart[];
  onPartsChange?: (parts: PromptInputPart[]) => void;
  value?: never;
  onChange?: never;
};
 
type PromptInputRef = { clear: () => void; focus: () => void };

PromptInputProps intersects the shared form/composer props with exactly one of PromptInputTextProps or PromptInputPartsProps.