PromptInput
A chat input bar with slash commands, file mentions, and AI SDK integration
A chat input bar with slash command autocomplete, file mention suggestions, command history, and direct Vercel AI SDK integration. Supports both a self-contained default layout and a fully composable compound mode.
Run demo
bunx @gridland/demo prompt-inputcurl -fsSL https://raw.githubusercontent.com/thoughtfulllc/gridland/main/scripts/run-demo.sh | bash -s prompt-inputInstallation
bunx shadcn@latest add @gridland/prompt-inputUsage
import { PromptInput } from "@/components/ui/prompt-input"
import { useKeyboard } from "@gridland/utils"<PromptInput
placeholder="Message Claude..."
useKeyboard={useKeyboard}
onSubmit={(msg) => console.log("Sent:", msg.text)}
/>Usage with AI SDKs
onSubmit receives { text: string }. Map it to your SDK of choice.
Vercel AI SDK
Direct pass-through — sendMessage accepts { text } natively.
import { useChat } from "@ai-sdk/react"
const { status, sendMessage, stop } = useChat({ /* transport config */ })
<PromptInput
status={status}
onSubmit={sendMessage}
onStop={stop}
useKeyboard={useKeyboard}
/>Anthropic SDK
const [status, setStatus] = useState<ChatStatus>("ready")
<PromptInput
status={status}
onSubmit={async (msg) => {
setStatus("submitted")
const stream = anthropic.messages.stream({
model: "claude-sonnet-4-20250514",
messages: [...history, { role: "user", content: msg.text }],
})
setStatus("streaming")
for await (const event of stream) { /* handle deltas */ }
setStatus("ready")
}}
onStop={() => { stream.abort(); setStatus("ready") }}
useKeyboard={useKeyboard}
/>OpenAI SDK
<PromptInput
status={status}
onSubmit={async (msg) => {
setStatus("submitted")
const stream = await openai.chat.completions.create({
model: "gpt-4o",
messages: [...history, { role: "user", content: msg.text }],
stream: true,
})
setStatus("streaming")
for await (const chunk of stream) { /* handle deltas */ }
setStatus("ready")
}}
useKeyboard={useKeyboard}
/>Examples
Slash Commands
Provide a commands array for / autocomplete.
<PromptInput
commands={[
{ cmd: "/help", desc: "Show commands" },
{ cmd: "/model", desc: "Switch model" },
{ cmd: "/clear", desc: "Clear conversation" },
]}
useKeyboard={useKeyboard}
onSubmit={(msg) => console.log(msg.text)}
/>File Mentions
Provide a files array for @ mention autocomplete.
<PromptInput
files={["src/index.ts", "src/routes.ts", "package.json"]}
useKeyboard={useKeyboard}
onSubmit={(msg) => console.log(msg.text)}
/>Custom Suggestion Provider
Override the built-in / and @ suggestions with your own logic.
<PromptInput
getSuggestions={(value) => {
if (value.startsWith("#")) {
return [
{ text: "#bug", desc: "Bug report" },
{ text: "#feature", desc: "Feature request" },
].filter((s) => s.text.startsWith(value))
}
return []
}}
useKeyboard={useKeyboard}
onSubmit={(msg) => console.log(msg.text)}
/>Controlled
Use value and onChange to control the input externally.
const [value, setValue] = useState("")
<PromptInput
value={value}
onChange={setValue}
useKeyboard={useKeyboard}
onSubmit={(msg) => console.log(msg.text)}
/>Model Label
Display the active model name below the input.
<PromptInput
model="claude-sonnet-4-20250514"
useKeyboard={useKeyboard}
onSubmit={(msg) => console.log(msg.text)}
/>Disabled (legacy)
When not using status, use disabled and disabledText directly.
<PromptInput
disabled
disabledText="Generating..."
useKeyboard={useKeyboard}
/>Compound Components
For full control over layout, pass children to enter compound mode. Subcomponents
read state from PromptInput via context — no prop drilling needed.
import { PromptInput } from "@/components/ui/prompt-input"
<PromptInput
status={status}
onSubmit={handleSubmit}
onStop={stop}
useKeyboard={useKeyboard}
>
<PromptInput.Divider />
<PromptInput.Suggestions />
<PromptInput.Textarea />
<PromptInput.Model />
<PromptInput.StatusText />
<PromptInput.Submit />
<PromptInput.Divider />
</PromptInput>Subcomponents
| Component | Description |
|---|---|
PromptInput.Textarea | Prompt character + text with cursor |
PromptInput.Suggestions | Autocomplete dropdown (slash commands, @mentions) |
PromptInput.Submit | Status indicator: ⏎ ready, ◐ submitted, ■ streaming, ✕ error |
PromptInput.Divider | Horizontal rule (─) |
PromptInput.StatusText | Error text shown when status is "error" |
PromptInput.Model | Muted model label shown below the input |
Provider
Wrap your app in PromptInputProvider to lift input state outside of
PromptInput. This lets you read or modify the input value and suggestions from
sibling components.
import { PromptInputProvider, usePromptInputController } from "@/components/ui/prompt-input"
<PromptInputProvider initialInput="">
<Sidebar />
<PromptInput useKeyboard={useKeyboard} onSubmit={handleSubmit} />
</PromptInputProvider>usePromptInputController
Access lifted state from anywhere inside the provider tree.
const controller = usePromptInputController()
controller.textInput.value // current text
controller.textInput.setValue(v) // set text
controller.textInput.clear() // clear text
controller.suggestions.suggestions // current suggestions
controller.suggestions.selectedIndex // selected index
controller.suggestions.setSuggestions(s) // set suggestions
controller.suggestions.setSelectedIndex(i)
controller.suggestions.clear()usePromptInput
Access rendering state from within any compound subcomponent.
import { usePromptInput } from "@/components/ui/prompt-input"
function MyCustomStatus() {
const { status, value, disabled } = usePromptInput()
return <text>{status}: {value.length} chars</text>
}Controls
- Enter: Submit message (or accept suggestion)
- Tab: Cycle through suggestions
- ↑/↓: Navigate suggestions or command history
- Escape: Dismiss suggestions, or stop generation when streaming
- /: Trigger slash command suggestions
- @: Trigger file mention suggestions
API Reference
PromptInput
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Controlled input value |
defaultValue | string | "" | Default value for uncontrolled mode |
onSubmit | (message: { text: string }) => void | Promise<void> | - | Called on submit. Clears on resolve, preserves on reject. |
onChange | (text: string) => void | - | Called when input value changes |
placeholder | string | "Type a message..." | Placeholder text when empty |
prompt | string | "❯ " | Prompt character before input |
promptColor | string | theme.muted | Color of the prompt character |
status | ChatStatus | - | AI chat status. Drives disabled state and hint text. |
onStop | () => void | - | Called when Escape is pressed during streaming |
submittedText | string | "Thinking..." | Hint text when status is "submitted" |
streamingText | string | "Generating..." | Hint text when status is "streaming" |
errorText | string | "An error occurred. Try again." | Text shown when status is "error" |
disabled | boolean | false | Disable input. Ignored when status is provided. |
disabledText | string | "Generating..." | Text shown when disabled. Ignored when status is provided. |
commands | { cmd: string; desc?: string }[] | [] | Slash commands for autocomplete |
files | string[] | [] | File paths for @ mention autocomplete |
getSuggestions | (value: string) => Suggestion[] | - | Custom suggestion provider — overrides commands/files |
maxSuggestions | number | 5 | Max visible suggestions |
enableHistory | boolean | true | Enable command history with ↑/↓ |
model | string | - | Model name displayed below the input |
showDividers | boolean | true | Show horizontal dividers above and below input |
autoFocus | boolean | false | Auto-focus the canvas on mount for keyboard events |
useKeyboard | (handler: (event: any) => void) => void | - | Keyboard hook from @opentui/react |
children | ReactNode | - | When provided, enables compound mode |
PromptInputProvider
| Prop | Type | Default | Description |
|---|---|---|---|
initialInput | string | "" | Initial text input value |
children | ReactNode | - | Components that can access the provider context |
Suggestion
| Field | Type | Description |
|---|---|---|
text | string | Suggestion text |
desc | string? | Optional description |
ChatStatus
| Value | Description |
|---|---|
"ready" | Input enabled, accepts user input |
"submitted" | Input disabled, shows submitted text |
"streaming" | Input disabled, shows streaming text, Escape calls onStop |
"error" | Input enabled, shows error indicator |