Prompt Input
@cloudflare/kumo

Prompt Input

A composable form for AI prompt entry. Combines a growing textarea, a footer for actions, and an optional header for context references. Press Enter to submit or Shift+Enter for a new line. The textarea auto-resizes with content up to a max height, then scrolls.

import { useState } from "react";
import { Button, DropdownMenu } from "@cloudflare/kumo";
import { FileIcon, XIcon } from "@phosphor-icons/react";
import { PromptInput } from "../kumo/prompt-input";

// ---------------------------------------------------------------------------
// Prompt Input — full-fledged, base size
// ---------------------------------------------------------------------------

/** Full-featured prompt input with context badges and a radio dropdown. */
export function PromptInputDemo() {
  const [value, setValue] = useState("");
  const [contexts, setContexts] = useState<string[]>(CONTEXTS);
  const [model, setModel] = useState<Model>("balanced");

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setValue("");
  };

  const removeContext = (id: string) => {
    setContexts((prev) => prev.filter((c) => c !== id));
  };

  const selected = MODEL_OPTIONS.find((o) => o.value === model)!;
  const SelectedIcon = selected.icon;

  return (
    <div className="w-full max-w-xl">
      <PromptInput onSubmit={handleSubmit}>
        <PromptInput.Header>
          {contexts.map((id) => (
            <Button
              icon={FileIcon}
              key={id}
              variant="outline"
              size="sm"
              onClick={() => removeContext(id)}
              aria-label={`Remove ${id}`}
              className="shadow-none rounded-full"
            >
              {id}
              <XIcon size={12} />
            </Button>
          ))}
        </PromptInput.Header>
        <PromptInput.Textarea
          value={value}
          onChange={(e) => setValue(e.target.value)}
          autoResize
        />
        <PromptInput.Footer>
          <DropdownMenu>
            <DropdownMenu.Trigger
              render={
                <Button
                  aria-label="Select model"
                  variant="ghost"
                  size="sm"
                  icon={<SelectedIcon />}
                  className="rounded-full text-kumo-subtle hover:text-kumo-default"
                >
                  {selected.label}
                </Button>
              }
            />
            <DropdownMenu.Content side="top" align="start">
              <DropdownMenu.RadioGroup
                value={model}
                onValueChange={(v) => setModel(v as Model)}
              >
                {MODEL_OPTIONS.map((opt) => (
                  <DropdownMenu.RadioItem
                    key={opt.value}
                    value={opt.value}
                    icon={opt.icon}
                  >
                    {opt.label}
                    <DropdownMenu.RadioItemIndicator />
                  </DropdownMenu.RadioItem>
                ))}
              </DropdownMenu.RadioGroup>
            </DropdownMenu.Content>
          </DropdownMenu>
          <PromptInput.SubmitButton disabled={!value.trim()} />
        </PromptInput.Footer>
      </PromptInput>
    </div>
  );
}

Prompt Input Large

The lg size increases text size, padding, and corner radius for prominent placements like landing pages or full-screen chat views.

import { useState } from "react";
import { Button, DropdownMenu } from "@cloudflare/kumo";
import { FileIcon, XIcon } from "@phosphor-icons/react";
import { PromptInput } from "../kumo/prompt-input";

// ---------------------------------------------------------------------------
// Prompt Input — full-fledged, large size
// ---------------------------------------------------------------------------

/** Full-featured large prompt input with context badges and a radio dropdown. */
export function PromptInputLargeDemo() {
  const [value, setValue] = useState("");
  const [contexts, setContexts] = useState<string[]>(CONTEXTS);
  const [model, setModel] = useState<Model>("balanced");

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setValue("");
  };

  const removeContext = (id: string) => {
    setContexts((prev) => prev.filter((c) => c !== id));
  };

  const selected = MODEL_OPTIONS.find((o) => o.value === model)!;
  const SelectedIcon = selected.icon;

  return (
    <div className="w-full max-w-xl">
      <PromptInput size="lg" onSubmit={handleSubmit}>
        <PromptInput.Header>
          {contexts.map((id) => (
            <Button
              icon={FileIcon}
              key={id}
              variant="outline"
              size="sm"
              onClick={() => removeContext(id)}
              aria-label={`Remove ${id}`}
              className="rounded-full shadow-none"
            >
              {id}
              <XIcon size={12} />
            </Button>
          ))}
        </PromptInput.Header>
        <PromptInput.Textarea
          value={value}
          onChange={(e) => setValue(e.target.value)}
          autoResize
        />
        <PromptInput.Footer>
          <DropdownMenu>
            <DropdownMenu.Trigger
              render={
                <Button
                  aria-label="Select model"
                  variant="ghost"
                  icon={<SelectedIcon />}
                  className="rounded-full shadow-none text-kumo-subtle hover:text-kumo-default"
                >
                  {selected.label}
                </Button>
              }
            />
            <DropdownMenu.Content side="top" align="start">
              <DropdownMenu.RadioGroup
                value={model}
                onValueChange={(v) => setModel(v as Model)}
              >
                {MODEL_OPTIONS.map((opt) => (
                  <DropdownMenu.RadioItem
                    key={opt.value}
                    value={opt.value}
                    icon={opt.icon}
                  >
                    {opt.label}
                    <DropdownMenu.RadioItemIndicator />
                  </DropdownMenu.RadioItem>
                ))}
              </DropdownMenu.RadioGroup>
            </DropdownMenu.Content>
          </DropdownMenu>
          <PromptInput.SubmitButton disabled={!value.trim()} />
        </PromptInput.Footer>
      </PromptInput>
    </div>
  );
}

Compact

The compact variant arranges everything in a single row — useful for sidebars, toolbars, or inline chat inputs. The header slot is hidden automatically in this variant. Clicking anywhere on the container focuses the textarea.

import { useState } from "react";
import { Button, DropdownMenu } from "@cloudflare/kumo";
import { PromptInput } from "../kumo/prompt-input";

// ---------------------------------------------------------------------------
// Compact — base size
// ---------------------------------------------------------------------------

/** Compact prompt input — base size with model dropdown. */
export function PromptInputCompactDemo() {
  const [value, setValue] = useState("");
  const [model, setModel] = useState<Model>("balanced");

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setValue("");
  };

  const selected = MODEL_OPTIONS.find((o) => o.value === model)!;
  const SelectedIcon = selected.icon;

  return (
    <div className="w-full max-w-md">
      <PromptInput variant="compact" onSubmit={handleSubmit}>
        <DropdownMenu>
          <DropdownMenu.Trigger
            render={
              <Button
                aria-label="Select model"
                variant="outline"
                size="sm"
                shape="circle"
                icon={<SelectedIcon />}
              />
            }
          />
          <DropdownMenu.Content side="top" align="start">
            <DropdownMenu.RadioGroup
              value={model}
              onValueChange={(v) => setModel(v as Model)}
            >
              {MODEL_OPTIONS.map((opt) => (
                <DropdownMenu.RadioItem
                  key={opt.value}
                  value={opt.value}
                  icon={opt.icon}
                >
                  {opt.label}
                  <DropdownMenu.RadioItemIndicator />
                </DropdownMenu.RadioItem>
              ))}
            </DropdownMenu.RadioGroup>
          </DropdownMenu.Content>
        </DropdownMenu>
        <PromptInput.Textarea
          value={value}
          onChange={(e) => setValue(e.target.value)}
        />
        <PromptInput.Footer>
          <PromptInput.SubmitButton disabled={!value.trim()} />
        </PromptInput.Footer>
      </PromptInput>
    </div>
  );
}

Compact Large

Compact layout at the lg size — larger hit target and text for touch-friendly or high-prominence inline inputs.

import { useState } from "react";
import { Button, DropdownMenu } from "@cloudflare/kumo";
import { PromptInput } from "../kumo/prompt-input";

// ---------------------------------------------------------------------------
// Compact — large size
// ---------------------------------------------------------------------------

/** Compact prompt input — large size with model dropdown. */
export function PromptInputCompactLargeDemo() {
  const [value, setValue] = useState("");
  const [model, setModel] = useState<Model>("balanced");

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setValue("");
  };

  const selected = MODEL_OPTIONS.find((o) => o.value === model)!;
  const SelectedIcon = selected.icon;

  return (
    <div className="w-full max-w-md">
      <PromptInput variant="compact" size="lg" onSubmit={handleSubmit}>
        <DropdownMenu>
          <DropdownMenu.Trigger
            render={
              <Button
                aria-label="Select model"
                variant="outline"
                shape="circle"
                icon={<SelectedIcon />}
              />
            }
          />
          <DropdownMenu.Content side="top" align="start">
            <DropdownMenu.RadioGroup
              value={model}
              onValueChange={(v) => setModel(v as Model)}
            >
              {MODEL_OPTIONS.map((opt) => (
                <DropdownMenu.RadioItem
                  key={opt.value}
                  value={opt.value}
                  icon={opt.icon}
                >
                  {opt.label}
                  <DropdownMenu.RadioItemIndicator />
                </DropdownMenu.RadioItem>
              ))}
            </DropdownMenu.RadioGroup>
          </DropdownMenu.Content>
        </DropdownMenu>
        <PromptInput.Textarea
          value={value}
          onChange={(e) => setValue(e.target.value)}
        />
        <PromptInput.Footer>
          <PromptInput.SubmitButton disabled={!value.trim()} />
        </PromptInput.Footer>
      </PromptInput>
    </div>
  );
}

Composition

PromptInput is fully composable with standard Kumo components. The examples above demonstrate context badges using Button in the header, and model selectors using DropdownMenu.RadioGroup in the footer. Only the structural sub-components are part of the block:

Sub-componentPurpose
PromptInput.HeaderContext area for badges or attachments — hidden in compact variant
PromptInput.TextareaAuto-resizing textarea with Enter-to-submit. Default max height is 256px (max-h-64); override via className="max-h-96" etc.
PromptInput.FooterAction bar below the textarea
PromptInput.SubmitButtonSubmit/stop button that auto-scales with the parent size

Keyboard Controls

KeyAction
EnterSubmit the form
Shift+EnterInsert a new line

Auto-resize

Pass autoResize to PromptInput.Textarea to grow the textarea with its content. It caps at the element’s CSS max-height (default max-h-64 / 256px), then scrolls. Override the cap via className:

{/* Default: 256px max */}
<PromptInput.Textarea autoResize />

{/* Custom: 384px max */}
<PromptInput.Textarea autoResize className="max-h-96" />

{/* No limit */}
<PromptInput.Textarea autoResize className="max-h-none" />

Installation

PromptInput is an AI block — a CLI-installed component that you own and can customize.

1. Initialize Kumo config (first time only)

npx @cloudflare/kumo init

2. Install the block

npx @cloudflare/kumo add PromptInput

3. Import from your local path

import { PromptInput } from "./components/kumo/prompt-input/prompt-input";

API Reference

PropTypeDefaultDescription
childrenReactNode--
classNamestring--
idstring--
langstring--
titlestring--
size"base" | "lg""base"-
variant"default" | "compact""default"-