A single-line text input component.
Tela.Component.TextInput accepts printable character input and supports
cursor navigation, deletion, and optional password masking. It respects
focus state — when blurred, key events are ignored and the cursor is hidden.
Usage
defmodule MyApp do
use Tela
alias Tela.Component.TextInput
alias Tela.Frame
@impl Tela
def init(_args) do
ti = TextInput.init(placeholder: "Pikachu", char_limit: 156) |> TextInput.focus()
{%{input: ti}, TextInput.blink_cmd(ti)}
end
@impl Tela
def handle_event(model, %Tela.Key{key: key}) when key in [:enter, :escape, {:ctrl, "c"}] do
{model, :quit}
end
def handle_event(model, key) do
{input, cmd} = TextInput.handle_event(model.input, key)
{%{model | input: input}, cmd}
end
@impl Tela
def handle_info(model, msg) do
{input, cmd} = TextInput.handle_blink(model.input, msg)
{%{model | input: input}, cmd}
end
@impl Tela
def view(model) do
header = Frame.new("What's your favorite Pokémon?\n\n")
footer = Frame.new("\n\n(esc to quit)")
Frame.join([header, TextInput.view(model.input), footer], separator: "")
end
endFocus
A TextInput is blurred by default. Call focus/1 to enable input and show
the cursor. Call blur/1 to hide the cursor and stop accepting key events.
In a multi-field form, only one input should be focused at a time.
Cursor
TextInput uses a virtual cursor — a reverse-video character embedded
directly in the content string at the cursor position. The frame returned
by view/1 always has cursor: nil; the real terminal cursor is kept hidden
so it does not overwrite the virtual cursor's colour.
The cursor character is the grapheme under the cursor (or a space when the
cursor is past the last grapheme), rendered with
Style.reverse(focused_style). This produces a coloured reverse-video block
that inherits the focused colour.
Cursor visibility is controlled by cursor_mode:
:blink— the cursor blinks on and off every 530 ms (default). The application timer drives this by togglingcursor_visible, which gates whether the reverse-video character appears incontent.:static— the cursor is always visible.:hidden— no cursor character is embedded incontent.
Use blink_cmd/1 to start the blink loop and handle_blink/2 to process
tick messages. The stale-tick guard (same pattern as Tela.Component.Spinner)
ensures that in-flight blink tasks from a previous mode or replaced input are
silently discarded. Use set_cursor_mode/2 to change modes at runtime.
Echo modes
Set echo_mode: to control how the value is displayed:
:normal— characters rendered as typed (default).:password— each character replaced byecho_char(default"*"). Passecho_char: "•"for a bullet. The underlying value is stored unmasked and returned byvalue/1.:none— nothing is rendered; the input acts as a fully invisible field. Useful for pinentry-style inputs. The underlying value is still stored and returned unmasked byvalue/1.
Styling
focused_style and blurred_style are Tela.Style.t() values applied to
the prompt and text depending on focus state. Both default to Tela.Style.new().
Summary
Functions
Returns a {:task, fun} cmd that sleeps 530 ms then
sends {:text_input_blink, id} back to the runtime.
Blurs the input, disabling key events and hiding the cursor.
Focuses the input, enabling key events and showing the cursor.
Processes a blink tick message for this input.
Handles a key event. Returns {new_model, cmd}.
Initialises a new text input model.
Sets the cursor mode.
Replaces the current value, clamping to char_limit if set, and moves the
cursor to the end of the new value.
Returns the current value of the input.
Renders the input as a Tela.Frame.
Types
@type t() :: %Tela.Component.TextInput{ blurred_style: Tela.Style.t(), char_limit: non_neg_integer(), cursor: non_neg_integer(), cursor_id: non_neg_integer(), cursor_mode: :blink | :static | :hidden, cursor_visible: boolean(), echo_char: String.t(), echo_mode: :normal | :password | :none, focused: boolean(), focused_style: Tela.Style.t(), placeholder: String.t(), prompt: String.t(), value: String.t() }
The text input model. Build with init/1; treat as opaque.
Functions
Returns a {:task, fun} cmd that sleeps 530 ms then
sends {:text_input_blink, id} back to the runtime.
Returns nil when cursor_mode is not :blink — no blink loop is needed
for :static or :hidden modes.
Pass the result as the cmd in your app's init/1 or handle_info/2 return.
Route the resulting tick message to handle_blink/2.
Blurs the input, disabling key events and hiding the cursor.
Focuses the input, enabling key events and showing the cursor.
Processes a blink tick message for this input.
Matches {:text_input_blink, id} where id equals the input's current
cursor_id. On a match, toggles cursor_visible, rotates cursor_id, and
returns {new_input, blink_cmd(new_input)} to re-arm the loop.
Any non-matching message — including stale ticks from a previous mode or a
replaced input — returns {input, nil} unchanged.
@spec handle_event(t(), Tela.Key.t()) :: {t(), Tela.cmd()}
Handles a key event. Returns {new_model, cmd}.
Key events are ignored when the input is blurred. When cursor_mode is
:blink, any focused keypress resets the cursor to visible and re-arms the
blink timer, so the cursor is always immediately visible after typing or
navigating.
Initialises a new text input model.
Options
prompt:— prefix rendered before the input text (default"> ").placeholder:— text shown when value is empty and the input is blurred (default"").char_limit:— maximum number of graphemes accepted;0means no limit (default0).echo_mode:—:normal,:password, or:none. Password mode masks each character withecho_char. None mode renders nothing (default:normal).echo_char:— the masking character used in password mode (default"*").cursor_mode:—:blink,:static, or:hidden(default:blink).focused_style:—Tela.Style.t()applied to prompt and text when focused (defaultTela.Style.new()).blurred_style:—Tela.Style.t()applied to prompt and text when blurred (defaultTela.Style.new()).
Sets the cursor mode.
Accepted values: :blink, :static, :hidden.
Rotates cursor_id so any in-flight blink task from the previous mode
becomes stale and is silently discarded by handle_blink/2. Resets
cursor_visible to true so that switching back to :blink starts with
the cursor shown.
Replaces the current value, clamping to char_limit if set, and moves the
cursor to the end of the new value.
Returns the current value of the input.
@spec view(t()) :: Tela.Frame.t()
Renders the input as a Tela.Frame.
Uses a virtual cursor — a reverse-video character embedded in the content
string at the cursor position. The frame cursor is always nil; the real
terminal cursor is kept hidden so it does not overwrite the virtual cursor's
colour.
When the cursor is visible (focused, cursor_mode not :hidden, and blink
phase on), the grapheme under the cursor — or a space when the cursor is past
the last grapheme — is rendered with Style.reverse(focused_style). This
produces a coloured reverse-video block that inherits the focused colour.
When blurred, cursor_mode is :hidden, or the blink phase is off, no
cursor character is embedded and content is the plain styled text.