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-component | Purpose |
|---|---|
PromptInput.Header | Context area for badges or attachments — hidden in compact variant |
PromptInput.Textarea | Auto-resizing textarea with Enter-to-submit. Default max height is 256px (max-h-64); override via className="max-h-96" etc. |
PromptInput.Footer | Action bar below the textarea |
PromptInput.SubmitButton | Submit/stop button that auto-scales with the parent size |
Keyboard Controls
| Key | Action |
|---|---|
Enter | Submit the form |
Shift+Enter | Insert 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 init2. Install the block
npx @cloudflare/kumo add PromptInput3. Import from your local path
import { PromptInput } from "./components/kumo/prompt-input/prompt-input";API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | - | - |
| className | string | - | - |
| id | string | - | - |
| lang | string | - | - |
| title | string | - | - |
| size | "base" | "lg" | "base" | - |
| variant | "default" | "compact" | "default" | - |