← Back to Home • Next: Input — Styles →
How the Input control behaves while it is running: keyboard, the order in which restrictions and
validation apply, history, autocomplete, and the small details that matter in real apps.
Name: John_ ← prompt + typed text + cursor
First and last name ← description (optional / dynamic)
Current length: 4 ← ChangeDescription output (optional)
Value is invalid ← error line, only when validation fails
Enter: confirm Esc: cancel F3: history ← tooltip (toggle with F1 / Ctrl+F1)
Every region can be recolored — see Styles.
| Key | Action |
|---|---|
| Any printable character | Insert at the cursor (subject to AcceptInput and MaxLength) |
← / → |
Move one character |
Home / End |
Start / end of line |
Backspace |
Delete character before the cursor |
Delete |
Delete character at the cursor |
Insert |
Toggle insert / overwrite mode |
There is no plain (non-Emacs) word-motion or word-deletion binding: Ctrl+←/Ctrl+→,
Ctrl+Backspace, and Ctrl+Delete do not move/delete by word. Word motion/deletion is
Emacs-only — Alt+F/Alt+B to move, Ctrl+W/Alt+D to delete — see the warning below.
| Key | Action |
|---|---|
Enter |
Confirm — runs validation, then returns the value |
Esc |
Abort (when the abort key is enabled) → IsAborted == true |
Tab / Shift+Tab |
Autocomplete — behavior differs by mode, see Autocomplete below |
F3 |
Open history navigation (when history is enabled) |
F1 |
Cycle tooltip content |
Ctrl+F1 |
Show / hide the tooltip |
When Emacs key bindings are enabled (
PromptPlus.Console.EnabledEmacs = true), the text also responds toCtrl+A/E/B/F/D/K/U/W/Tand friends (noCtrl+Y— there’s no yank/kill-buffer support here). See Keyboard Bindings for the full table.
The restrictions apply in a fixed order, so it helps to picture the pipeline:
key pressed
│
├─ is it a control/navigation key? --→ handled (move, delete, confirm, …)
│
└─ printable character
│
├─ InputToCase applied FIRST (upper/lower) — before AcceptInput ever sees the char
├─ AcceptInput(transformedChar) == false? --→ ignored
├─ length already == MaxLength? --→ ignored
└─ inserted at cursor
Consequently:
InputToCase runs BEFORE AcceptInput, not after. AcceptInput receives the
already-cased character, never the raw one the user typed. A predicate that only accepts one
case (e.g. InputToCase: CaseOptions.Uppercase combined with AcceptInput: char.IsLower) will
silently reject every character, since it never sees a lowercase char to accept. Write
AcceptInput predicates that expect the target case (or are case-insensitive) when combining the
two.AcceptInput and MaxLength are still preventive — they stop bad input from ever appearing;
they just see it after the case transform, not before.Pressing Enter runs this sequence:
PredicateValid /
PredicateValidAsync, if configured — against the raw
typed value, even if it’s empty.DefaultIfEmpty’s
value is substituted; the control closes and returns ResultPrompt<string> with
IsAborted == false.⚠️
DefaultIfEmptyruns AFTER validation, not before. APredicateValidthat rejects empty input will reject an empty field beforeDefaultIfEmptyever gets a chance to substitute its value — the substitution only happens once the raw (possibly empty) value has already passed validation. If you want an empty field to be valid, your predicate must accept empty strings.
⚠️ Validation only runs on confirm, never per keystroke. Use
AcceptInputfor per-keystroke rules andPredicateValidfor whole-value rules (length, format, uniqueness).
When EnableHistory(filename, …) is set:
filename.FilterType, MinPrefixLength, PageSize control this). While that list is open: ↑/↓ move
between entries, Page Up/Page Down move between pages, Ctrl+Home/Ctrl+End jump to the
first/last entry, and F3 again closes the list back to normal editing.Default(string.Empty, useDefaultHistory: true), the most recent entry is pre-loaded as the
starting value.ExpirationTime; the store keeps at most MaxItems.⚠️
Ctrl+Delete, while the history list is open, deletes the entire on-disk history file — not a single entry, the whole store. This is a real, destructive, and easy-to-trigger-by-accident action (it’s the same physical key combo a user might reach for expecting “delete word”).
You can also manage a history store directly with PromptPlus.Controls.History(filename) — add,
save, or remove entries programmatically (used in the samples to seed reproducible data).
⚠️ Do not enable history on secret fields — confirmed values are written to disk in the store.
When a suggestion handler is set, the provider is called once the
typed length reaches MinimumSuggestionLength. What happens
next depends on autocomplete, and the two modes behave quite differently — this isn’t just “same
feature, different key”:
autocomplete: true (default) — there is no separate suggestion list. Each Tab press
replaces the buffer directly with the next match (wrapping around at the end); Shift+Tab has no
effect in this mode. This applies regardless of how many matches the provider returned — “apply
immediately for a single match” is not the real rule; every match, one or many, is cycled the same
way.autocomplete: false — pressing Tab or Shift+Tab switches into a separate suggestion-list
mode. Once there: ↑ / ↓ move the highlight, Tab accepts the highlighted suggestion and
returns to normal editing, Shift+Tab cancels and restores the text you had before entering the
list.History (F3) and suggestions (Tab) are independent features and can be used together.
Set per instance via Options(...), or globally on
PromptPlus.Config:
| Option | Effect on Input |
|---|---|
EnabledAbortKey(false) |
Removes Esc — the user must confirm |
HideAfterFinish(true) |
Erases the field after confirm — the whole control is erased, not just the interactive part |
HideOnAbort(true) |
Erases the field after Esc |
ShowTooltip(false) |
Hides the keyboard hint line |
Prompt(...) / Description(...) |
Overrides the prompt / description text |
… is shown, but .Content always holds the full untruncated text..Content == "". .Content holds whatever is currently
in the text buffer at the moment of abort — a seeded Default the user never typed over, or
partially-typed text, not necessarily empty. Always branch on IsAborted before using the value.PredicateValidAsync.Options