appCN
← All components

Prompt Input

AI
new

The AI composer. Auto-grows with content, holds attachment chips, and morphs its send button into a stop with a spinning ring while generating.

Preview

Scan with Expo Go to run it live

Install Expo Goon Android or iPhone, scan, and the entire appCN gallery loads — real native motion & gestures, not a web shim.

Open in Expo Go Play Store — coming soon
https://expo.dev/preview/update?message=v1.0.0+initial&updateRuntimeVersion=1.0.0&createdAt=2026-05-28T20%3A07%3A21.247Z&slug=exp&projectId=a2d02caa-be26-436a-acd6-f3007862ba0a&group=3ca7e750-9506-4146-8394-1a16c3a917a8

Install

npx @app-cn/cli@latest add prompt-input

Recommended. Configures NativeWind + Reanimated and registers @app-cn on first run.

Anatomy

A rounded card containing an optional row of attachment chips, an auto-growing multiline TextInput, and a circular send button. The send button is the affordance: it animates between three states (disabled, ready, generating) and is the canonical control surface for AI chat composers.

The delight detail

The send glyph cross-fades into a stop square the moment generation starts, with a 2px progress ring spinning at 900ms/turn wrapped around it — one element doing the work of three.

Props

NameTypeDefaultDescription
valuestringControlled text value. Omit to use uncontrolled mode.
defaultValuestringInitial text in uncontrolled mode.
onChangeText(text: string) => voidFires on every keystroke.
onSubmit(text: string) => voidCalled with the trimmed message when the send button is tapped.
onStop() => voidCalled when the user taps the stop button while `generating` is true.
generatingbooleanfalseWhen true, the send button morphs into a stop with a spinning ring.
placeholderstring"Message appCN…"TextInput placeholder.
minHeightnumber24Minimum input height in px before auto-grow kicks in.
maxHeightnumber140Maximum height the input will grow to before scrolling.
attachmentsPromptAttachment[]Chips rendered above the input. Each is `{ id, label }`.
onAddAttachment() => voidWhen provided, a leading "+" button appears that calls this on tap.
onRemoveAttachment(id: string) => voidWhen provided, each chip renders a small "×" that calls this with its id.
disabledbooleanfalseGreys out the whole composer and disables all controls.
classNamestringExtra NativeWind classes merged onto the outer container.

Examples

Basic

Uncontrolled — clears itself on submit.

<PromptInput onSubmit={(text) => sendMessage(text)} />

Generating + stop

Flip `generating` to morph send into stop. `onStop` cancels the request.

const [generating, setGenerating] = React.useState(false);
return (
  <PromptInput
    generating={generating}
    onSubmit={async (text) => {
      setGenerating(true);
      await fetchReply(text);
      setGenerating(false);
    }}
    onStop={() => cancelInFlight()}
  />
);

With attachments

A "+" button appears when `onAddAttachment` is provided; chips animate in and out.

const [files, setFiles] = React.useState<PromptAttachment[]>([]);
return (
  <PromptInput
    attachments={files}
    onAddAttachment={() => pickFile().then((f) => setFiles((a) => [...a, f]))}
    onRemoveAttachment={(id) => setFiles((a) => a.filter((f) => f.id !== id))}
    onSubmit={(text) => sendMessage(text, files)}
  />
);

Accessibility

  • The send button's `accessibilityLabel` dynamically swaps between “Send message” and “Stop generating” — VoiceOver announces the current affordance.
  • `accessibilityState.disabled` reflects whether the button is actionable (empty input + not generating = disabled).
  • Attachment remove buttons use `accessibilityLabel={`Remove ${label}`}` so each chip is uniquely addressable.
  • Honors `useReducedMotion()` — the send-to-stop morph and ring spin both short-circuit when reduced motion is on.
  • Hit slop is 8px on all small icon buttons (send, attachment-remove) so they meet ≥44pt targets.