$ rezi
Widgets

Input

A single-line, controlled text input widget with cursor navigation and editing support.

A single-line, controlled text input widget with cursor navigation and editing support.

Usage

ui.input({
  id: "name",
  value: state.name,
  onInput: (value) => app.update((s) => ({ ...s, name: value })),
})

Props

PropTypeDefaultDescription
idstringrequiredUnique identifier for focus and event routing
valuestringrequiredCurrent input value (controlled)
accessibleLabelstring-Optional semantic label for focus announcements and debugging
disabledbooleanfalseDisable editing and dim appearance
styleTextStyle-Custom styling (merged with focus/disabled state)
onInput(value: string, cursor: number) => void-Callback when value changes
onBlur() => void-Callback when input loses focus
keystring-Reconciliation key for dynamic lists

Behavior

Inputs are focusable when enabled. Clicking the input focuses it. When focused:

  • Text entry inserts at cursor position
  • Left/Right move by grapheme cluster
  • Ctrl+Left/Ctrl+Right move by word boundaries
  • Home/End move to start/end of input
  • Shift+Left/Shift+Right extends selection by grapheme
  • Shift+Home/Shift+End extends selection to start/end
  • Shift+Ctrl+Left/Shift+Ctrl+Right extends selection by word
  • Ctrl+A selects all text
  • Ctrl+C copies active selection to system clipboard (OSC 52)
  • Ctrl+X cuts active selection to system clipboard (OSC 52)
  • Ctrl+Z undoes the last edit
  • Ctrl+Shift+Z or Ctrl+Y redoes the last undone edit
  • Backspace/Delete remove one grapheme cluster when no selection is active
  • Backspace/Delete delete the selected range when selection is active
  • Typing with an active selection replaces the selected range
  • Paste strips \r/\n (single-line input) and keeps tabs
  • Tab moves focus to next widget

Inputs are always controlled - the value prop determines what is displayed. For multi-line text, use ui.textarea.

Input Editor State

Input editing is grapheme-aware and internally tracks:

  • cursor: current caret offset at a grapheme boundary
  • selectionStart: anchor offset, or null when no selection is active
  • selectionEnd: active/caret offset, or null when no selection is active

Renderer integrations receive these through the runtime input editor result to support selection highlighting.

Examples

Controlled input

type State = { email: string };

app.view((state) =>
  ui.input({
    id: "email",
    value: state.email,
    onInput: (value) => app.update((s) => ({ ...s, email: value })),
  })
);

With useForm binding

ui.input(form.bind("email"));

Validation on blur

Use onBlur to trigger validation when the user leaves the field:

ui.input({
  id: "email",
  value: state.email,
  onInput: (value) => app.update((s) => ({ ...s, email: value })),
  onBlur: () => validateEmail(state.email),
})

With a field wrapper

Combine with field for labels and error display:

ui.field({
  label: "Email",
  required: true,
  error: state.errors.email,
  children: ui.input({
    id: "email",
    value: state.email,
    onInput: (v) => app.update((s) => ({ ...s, email: v })),
  }),
})

Unicode Handling

Text editing is based on grapheme clusters using a pinned Unicode version. This ensures:

  • Emoji and combined characters are handled as single units
  • Cursor movement is consistent across platforms
  • Deterministic behavior for any input string

On this page