TextInput
API reference for the TextInput component
A controlled single-line text input with cursor navigation, inline editing, and render prop support.
import { TextInput } from 'giggles/ui';
import { useState } from 'react';
function NameInput() {
const [value, setValue] = useState('');
return <TextInput label="Name:" value={value} onChange={setValue} placeholder="John Doe" />;
}API Reference
TextInput
Prop
Type
Keybindings
| Key | Action |
|---|---|
| Characters | Insert at cursor position |
← → | Move cursor |
Home End | Jump to start/end |
Backspace | Delete character before cursor |
Delete | Delete character at cursor |
Tab / Shift+Tab | Move to next/previous field |
Enter | Fire onSubmit if provided, otherwise available for parent keybindings |
Escape | Ignored (available for parent keybindings) |
TextInputRenderProps
The render prop receives pre-split text segments around the cursor.
Prop
Type
Examples
Multi-field form
Wrap multiple TextInput fields in a FocusScope and wire tab/shift+tab to next/prev — TextInput bubbles both keys automatically so the scope handles field switching with no extra logic. Put onSubmit on the last field to fire on Enter:
import { FocusScope, useFocusScope } from 'giggles';
import { TextInput } from 'giggles/ui';
import { useState } from 'react';
function ContactForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const scope = useFocusScope({
keybindings: ({ next, prev }) => ({ tab: next, 'shift+tab': prev }),
});
return (
<Box flexDirection="column" gap={1}>
<FocusScope handle={scope}>
<TextInput label="Name:" value={name} onChange={setName} placeholder="Jane Doe" />
<TextInput label="Email:" value={email} onChange={setEmail} placeholder="jane@example.com" />
<TextInput
label="Message:"
value={message}
onChange={setMessage}
onSubmit={() => submit({ name, email, message })}
placeholder="Your message..."
/>
</FocusScope>
</Box>
);
}Custom render
The render prop gives full control over how the input is displayed. The placeholder prop is forwarded into render props so custom renders can replicate the cursor-on-placeholder behaviour.
function SearchBar() {
const [query, setQuery] = useState('');
return (
<TextInput
value={query}
onChange={setQuery}
placeholder="Search components..."
render={({ before, cursorChar, after, focused, value, placeholder }) => {
const empty = value.length === 0;
let content;
if (focused && empty && placeholder) {
content = <Text><Text inverse>{placeholder[0]}</Text><Text dimColor>{placeholder.slice(1)}</Text></Text>;
} else if (focused) {
content = <Text>{before}<Text inverse>{cursorChar}</Text>{after}</Text>;
} else {
content = <Text dimColor={empty}>{empty ? placeholder : value}</Text>;
}
return (
<Box borderStyle="round" borderColor={focused ? 'cyan' : 'gray'} paddingX={1} gap={1} width={36}>
<Text color={focused ? 'cyan' : 'gray'}>/</Text>
{content}
</Box>
);
}}
/>
);
}