TextInput
A single-line text input with label, prompt, and validation
A single-line text input field with label, prompt prefix, placeholder, character count, and validation states.
Run demo
bunx @gridland/demo text-inputcurl -fsSL https://raw.githubusercontent.com/thoughtfulllc/gridland/main/scripts/run-demo.sh | bash -s text-inputInstallation
bunx create-gridland add text-inputUsage
import { TextInput } from "@/components/ui/text-input"const [name, setName] = useState("")
<TextInput
focusId="username"
autoFocus
label="Username"
value={name}
onChange={setName}
placeholder="enter your name"
prompt="> "
/>TextInput registers with the focus system via focusId. Wrap your app in
GridlandProvider (which mounts <FocusProvider selectable> implicitly) for
keyboard routing. Tab to focus the field, Enter to select (start editing),
then type. Escape exits edit mode.
Examples
Form
Multiple inputs composed into a form with keyboard navigation.
With onSubmit
Use onSubmit to handle Enter key presses.
const [email, setEmail] = useState("")
<TextInput
focusId="email"
autoFocus
label="Email"
value={email}
onChange={setEmail}
onSubmit={(v) => console.log("Submitted:", v)}
placeholder="user@example.com"
prompt="> "
/>Required
Show a required indicator (*) next to the label.
const [name, setName] = useState("")
<TextInput
focusId="name"
autoFocus
label="Name"
value={name}
onChange={setName}
placeholder="enter your name"
prompt="> "
required
/>Description
Display helper text inline next to the label.
const [email, setEmail] = useState("")
<TextInput
focusId="email"
autoFocus
label="Email"
value={email}
onChange={setEmail}
placeholder="user@example.com"
prompt="> "
description="We'll never share your email"
/>Error
Pass an error string to show a validation message. It replaces the description.
const [email, setEmail] = useState("")
<TextInput
focusId="email"
autoFocus
label="Email"
value={email}
onChange={setEmail}
placeholder="user@example.com"
prompt="> "
error="This field is required"
/>Max Length
Limit input length and show a character counter when the user starts typing.
const [bio, setBio] = useState("")
<TextInput
focusId="bio"
autoFocus
label="Bio"
value={bio}
onChange={setBio}
placeholder="tell us about yourself"
prompt="> "
maxLength={100}
/>Disabled
A disabled input ignores focus and keystrokes.
<TextInput
focusId="api-key"
label="API Key"
value="sk-1234...abcd"
placeholder="sk-..."
prompt="> "
disabled
/>Form with keyboard navigation
TextInput registers with the focus system via focusId, so composing multiple
inputs into a form is just a matter of giving each one its own id. Tab and
Shift-Tab cycle between them automatically — no external keyboard wiring
needed.
const FIELDS = [
{ id: "username", label: "Username" },
{ id: "email", label: "Email" },
{ id: "password", label: "Password" },
]
function MyForm() {
const [values, setValues] = useState(FIELDS.map(() => ""))
return (
<>
{FIELDS.map((field, i) => (
<TextInput
key={field.id}
focusId={field.id}
autoFocus={i === 0}
label={field.label}
value={values[i]}
onChange={(v) => setValues((prev) => prev.map((old, j) => j === i ? v : old))}
prompt="> "
/>
))}
</>
)
}Controls
- Tab / Shift-Tab: Move between fields
- Enter: Start editing the focused field (and submit on re-press)
- Escape: Exit edit mode
API Reference
TextInput
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | - | Field label shown above the input |
description | string | - | Helper text shown inline next to the label |
error | string | - | Error message — overrides description when set |
required | boolean | false | Show required indicator (*) on the label |
disabled | boolean | false | Disable the input |
value | string | required | Current value of the input |
onChange | (value: string) => void | - | Called on every keystroke |
onSubmit | (value: string) => void | - | Called when Enter is pressed |
placeholder | string | - | Hint text shown when the input is empty |
prompt | string | - | Prompt string before the input (e.g. "> ") |
focusId | string | auto-generated | Stable id for the focus system |
autoFocus | boolean | false | Focus this input on mount |
maxLength | number | - | Maximum characters allowed (shows counter when typing) |