giggles

TextInput

API reference for the TextInput component

A controlled single-line text input with cursor navigation, inline editing, and render prop support.

Basic Usage
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

KeyAction
CharactersInsert at cursor position
Move cursor
Home EndJump to start/end
BackspaceDelete character before cursor
DeleteDelete character at cursor
Tab / Shift+TabMove to next/previous field
EnterFire onSubmit if provided, otherwise available for parent keybindings
EscapeIgnored (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/prevTextInput bubbles both keys automatically so the scope handles field switching with no extra logic. Put onSubmit on the last field to fire on Enter:

Multi-field form
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.

Custom Render
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>
        );
      }}
    />
  );
}

On this page