Prompt Input

Chat-style prompt field with optional multiline layout, attachments (picker + chips), a send / stop control for streaming, and a composable action strip.

Preview

"use client";

import { useState } from "react";
import {
  AudioWave01Icon,
  Megaphone02Icon,
  RoboticIcon,
  Upload01Icon,
  ZapIcon,
} from "@hugeicons/core-free-icons";

import {
  PromptInput,
  PromptInputActions,
  PromptInputPopup,
  PromptInputPopupItem,
  PromptInputPressAction,
  PromptInputToggleAction,
} from "@/registry/promt-input";

export function PromptInputDemo() {
  const [agent, setAgent] = useState(false);
  const [streaming, setStreaming] = useState(false);

  return (
    <div className="flex w-full max-w-199 flex-col gap-4">
      <PromptInput
        isLoading={streaming}
        onStop={() => setStreaming(false)}
        onSubmit={() => setStreaming(true)}
        placeholder="Ask anything…"
      >
        <PromptInputActions>
          <PromptInputPressAction
            icon={AudioWave01Icon}
            tooltip="Transcribe"
            aria-label="Transcribe"
          />
          <PromptInputToggleAction
            icon={RoboticIcon}
            pressed={agent}
            onPressedChange={setAgent}
            tooltip="Agent mode"
            aria-label="Agent mode"
          />
          <PromptInputPopup>
            <PromptInputPopupItem
              icon={ZapIcon}
              onSelect={({ close }) => close()}
            >
              Files
            </PromptInputPopupItem>
            <PromptInputPopupItem
              icon={Upload01Icon}
              onSelect={({ close, openFilePicker }) => {
                openFilePicker();
                close();
              }}
            >
              Upload from device
            </PromptInputPopupItem>
            <PromptInputPopupItem
              icon={Megaphone02Icon}
              shortcut="@"
              submenu
              onSelect={({ close }) => close()}
            >
              Mention
            </PromptInputPopupItem>
          </PromptInputPopup>
        </PromptInputActions>
      </PromptInput>
    </div>
  );
}

Installation

Install the registry item into your project (same pattern as other Arrow UI components):

pnpm dlx @arrowui/cli@latest add promt-input

This copies registry/promt-input.tsx and merges npm dependencies listed in the manifest. You still need shadcn-style UI primitives in your app (see Requirements).

Requirements

The component imports:

  • @/components/ui/popover — Radix Popover
  • @/components/ui/tooltip — Radix Tooltip
  • @/lib/utilscn

Wrap your app (or subtree) with TooltipProvider from @/components/ui/tooltip if it is not already in your root layout. Tooltips are used for action buttons and the add (+) trigger.

Usage

import {
PromptInput,
PromptInputActions,
PromptInputPopup,
PromptInputPressAction,
} from "@/registry/promt-input";
<PromptInput
placeholder="Ask anything…"
onSubmit={(value) => console.log(value)}
/>

Variants

Default

Multiline field with scroll fades, top chrome strip, and avatar. Same state and handlers as small; layout differs.

Small

Single row: avatar, single-line textarea, actions, send.

"use client";

import { PromptInput } from "@/registry/promt-input";

export function PromptInputSmallDemo() {
  return (
    <div className="flex w-full max-w-199 justify-center">
      <PromptInput variant="small" placeholder="Compact row…" />
    </div>
  );
}
<PromptInput variant="small" placeholder="…" />

Streaming

While the assistant is streaming, set isLoading and wire onStop. The primary control switches from send (Arrow up) to Stop.

const [loading, setLoading] = useState(false);

<PromptInput
isLoading={loading}
onStop={() => setLoading(false)}
onSubmit={() => setLoading(true)}
/>
  • With isLoading, Enter calls onStop (same as clicking Stop).
  • Send is disabled when there is nothing to send; Stop stays enabled so the user can always cancel.

Custom actions

Pass a single PromptInputActions child to PromptInput to replace the default action strip. Keep the same building blocks or compose your own.

<PromptInput onSubmit={…}>
<PromptInputActions>
  <PromptInputPressAction
    icon={AudioWave01Icon}
    tooltip="Transcribe"
    aria-label="Transcribe"
  />
  <PromptInputPopup>
    <PromptInputPopupItem onSelect={({ close }) => close()}>
      Custom row
    </PromptInputPopupItem>
  </PromptInputPopup>
</PromptInputActions>
</PromptInput>

The send button is always rendered after your actions (you do not include it inside PromptInputActions).

Add menu

The + button opens a popover. Menu content is children of PromptInputPopup. If you omit children, the panel is empty—define rows with PromptInputPopupItem.

  • PromptInputPopupItem: optional icon, label (children), optional shortcut (e.g. "@"), optional submenu (chevron only; no nested menu yet), optional trailing, and onSelect receiving { close, openFilePicker }.
  • openFilePicker() opens the hidden file input (same as “upload” flows inside the default examples).

PromptInputAttachMenu is a deprecated alias of PromptInputPopup.

Hooks

  • usePromptInput() — must be used under PromptInput. Returns { openFilePicker } for custom buttons that should open the file dialog.
  • usePromptInputPopup() — must be used under PromptInputPopup (e.g. inside custom menu content). Returns { close, openFilePicker }.

Dark mode

The component uses semantic Tailwind tokens (bg-card, text-foreground, muted, popover, gradients from background, etc.). No extra prop—respects .dark / your theme variables in globals.css.

Props

PromptInput

PropTypeDefault
variant"default" | "small""default"
placeholderstring"Ask anything…"
onSubmit(value: string) => void
isLoadingbooleanfalse
onStop() => void
childrenReact.ReactNode

Pass children as PromptInputActions (single wrapper) to customize the action strip; otherwise built-in defaults (transcribe + PromptInputPopup) are used.

PromptInputPopup

PropTypeDefault
tooltipstring"Add"
triggerAriaLabelClosedstring"Add attachment"
triggerAriaLabelOpenstring"Close attachment menu"
childrenReact.ReactNode
contentClassNamestring
alignPopoverContent align"end"
sidePopoverContent side"bottom"
sideOffsetnumber10

PromptInputPopupItem

PropTypeDefault
iconHugeiconsIcon
childrenReact.ReactNode
shortcutReact.ReactNode
submenubooleanfalse
trailingReact.ReactNode
onSelect(payload: PromptInputPopupSelectPayload) => void
classNamestring

PromptInputPopupSelectPayload: { close: () => void; openFilePicker: () => void }.

PromptInputActions

Wrapper for the right-side action row. Accepts ComponentProps<"div"> (e.g. className, id). Renders with role="group" and data-slot="prompt-input-actions".

Press / toggle actions

PropTypeDefault
iconHugeiconsIcon
tooltipstring
aria-labelstring
onClick() => void
classNamestring
PropTypeDefault
iconHugeiconsIcon
pressedboolean
onPressedChange(next: boolean) => void
tooltipstring
aria-labelstring
classNamestring

Hooks (signatures)

PropTypeDefault
returns.openFilePicker() => void
PropTypeDefault
returns.close() => void
returns.openFilePicker() => void