Widget classes for use with InquirerApp.prompt() or prompts.multi(). These provide building blocks for complex flows.
from typing import Iterable
from inquirer_textual.widgets.InquirerWidget import InquirerWidget
from inquirer_textual.widgets.InquirerText import InquirerText
from inquirer_textual.widgets.InquirerSecret import InquirerSecret
from inquirer_textual.widgets.InquirerNumber import InquirerNumber
from inquirer_textual.widgets.InquirerConfirm import InquirerConfirm
from inquirer_textual.widgets.InquirerSelect import InquirerSelect
from inquirer_textual.widgets.InquirerCheckbox import InquirerCheckbox
from inquirer_textual.widgets.InquirerMulti import InquirerMultiBase class for all inquirer widgets.
from typing import Any
from textual.widgets import Widget
from textual.message import Message
class InquirerWidget(Widget):
"""Base class for all inquirer widgets."""
def __init__(self, mandatory: bool = True):
"""
Args:
mandatory: If False, allows Ctrl+C exit (returns command='ctrl+c', value=None)
"""
def current_value(self) -> Any:
"""Get current widget value. Implemented by subclasses."""
class Submit(Message):
"""Message sent when widget submitted."""
def __init__(self, value: Any, command: str | None = "select"):
self.value = value
self.command = commandText input widget with optional validation.
from typing import Iterable
from textual.validation import Validator
class InquirerText(InquirerWidget):
"""Text input widget."""
def __init__(
self,
message: str,
default: str = '',
validators: Validator | Iterable[Validator] | None = None
):
"""
Args:
message: Prompt message
default: Initial value in input field
validators: Textual validators for input validation
"""
def focus(self, scroll_visible: bool = True) -> Self:
"""Focus the input field."""
def current_value(self) -> str:
"""Get current input text."""from inquirer_textual.InquirerApp import InquirerApp
from inquirer_textual.widgets.InquirerText import InquirerText
from textual.validation import Function
# With InquirerApp
app = InquirerApp()
widget = InquirerText(
"Project name:",
default="my-project",
validators=Function(lambda s: len(s) > 0, "Cannot be empty")
)
result = app.prompt(widget)
# With prompts.multi
from inquirer_textual import prompts
result = prompts.multi([
InquirerText("First name:"),
InquirerText("Last name:")
])
if result.command == 'select':
first, last = result.valuePassword/secret input widget with masked display.
class InquirerSecret(InquirerWidget):
"""Secret/password input widget."""
def __init__(self, message: str):
"""
Args:
message: Prompt message
"""
def focus(self, scroll_visible: bool = True) -> Self:
"""Focus the input field."""
def current_value(self) -> str:
"""Get current value (actual text, not masked)."""widget = InquirerSecret("Enter password:")
result = app.prompt(widget)
if result.command == 'select':
password = result.value # Actual password stringNumber input widget for integers.
class InquirerNumber(InquirerWidget):
"""Number input widget for integers."""
def __init__(self, message: str):
"""
Args:
message: Prompt message
"""
def focus(self, scroll_visible: bool = True) -> Self:
"""Focus the input field."""
def current_value(self) -> str:
"""Get current value as string (validated as integer)."""widget = InquirerNumber("Enter port:")
result = app.prompt(widget)
if result.command == 'select':
port = int(result.value) # Convert to intYes/No confirmation widget.
class InquirerConfirm(InquirerWidget):
"""Yes/No confirmation widget."""
def __init__(
self,
message: str,
confirm_character: str = 'y',
reject_character: str = 'n',
default: bool = False,
mandatory: bool = True
):
"""
Args:
message: Prompt message
confirm_character: Character for confirmation (default: 'y')
reject_character: Character for rejection (default: 'n')
default: Default value; True shows (Y/n), False shows (y/N)
mandatory: If False, allows Ctrl+C exit
"""
def current_value(self) -> bool:
"""Get current boolean value."""widget = InquirerConfirm("Continue?", default=True)
result = app.prompt(widget)
if result.command == 'select':
if result.value:
proceed()Custom characters (e.g., Spanish):
widget = InquirerConfirm(
"¿Continuar?",
confirm_character='s', # sí
reject_character='n', # no
default=True
)Single selection widget.
class InquirerSelect(InquirerWidget):
"""Single selection from list widget."""
def __init__(
self,
message: str,
choices: list[str | Choice],
default: str | Choice | None = None,
mandatory: bool = True
):
"""
Args:
message: Prompt message
choices: List of string choices or Choice objects
default: Initial highlighted item
mandatory: If False, allows Ctrl+C exit
"""
def focus(self, scroll_visible: bool = True) -> Self:
"""Focus the list view."""
def current_value(self) -> str | Choice | None:
"""Get currently highlighted item."""from inquirer_textual.common.Choice import Choice
widget = InquirerSelect(
"Environment:",
[
Choice("Dev", data={"url": "localhost"}),
Choice("Prod", data={"url": "example.com"})
],
default="Dev"
)
result = app.prompt(widget)
if result.command == 'select':
env_url = result.value.data["url"]Multi-select checkbox widget.
class InquirerCheckbox(InquirerWidget):
"""Multi-select checkbox widget."""
def __init__(
self,
message: str,
choices: list[str | Choice],
enabled: list[str | Choice] | None = None
):
"""
Args:
message: Prompt message
choices: List of string choices or Choice objects
enabled: Pre-checked items (NOT FUNCTIONAL in v0.2.0)
"""
def focus(self, scroll_visible: bool = True) -> Self:
"""Focus the list view."""
def current_value(self) -> list[str | Choice]:
"""Get list of checked items."""
def action_toggle_selected(self):
"""Toggle checkbox for current item. Bound to spacebar."""widget = InquirerCheckbox(
"Select features:",
["Auth", "Database", "Caching", "Logging"]
)
result = app.prompt(widget)
if result.command == 'select':
for feature in result.value:
enable_feature(feature)Known Issue: enabled parameter does not work in v0.2.0.
Sequential multi-prompt widget.
class InquirerMulti(InquirerWidget):
"""Sequential multi-prompt widget."""
def __init__(self, widgets: list[InquirerWidget]):
"""
Args:
widgets: Widgets to display in sequence
"""multi_widget = InquirerMulti([
InquirerText("Name:"),
InquirerText("Email:"),
InquirerConfirm("Subscribe?")
])
app = InquirerApp()
result = app.prompt(multi_widget)
if result.command == 'select':
name, email, subscribe = result.valueon_mount)Submit message with value and commandfrom textual.validation import Function
def create_required_text(label: str):
"""Factory for required text widgets."""
return InquirerText(
label,
validators=Function(lambda s: len(s) > 0, "Required")
)
name_widget = create_required_text("Name:")
email_widget = create_required_text("Email:")def flow(app):
select_widget = InquirerSelect("Choose:", ["A", "B", "C"])
# Before prompt
print(f"Initial: {select_widget.current_value()}")
result = app.prompt(select_widget)
# After prompt
print(f"Final: {select_widget.current_value()}")
app.stop(result.value)def conditional_flow(app):
use_secret = True # Determined by some logic
if use_secret:
widget = InquirerSecret("Enter value:")
else:
widget = InquirerText("Enter value:")
result = app.prompt(widget)
app.stop(result.value)DEFAULT_VALIDATORS = [
Function(lambda s: len(s) >= 3, "Min 3 chars"),
Function(lambda s: ' ' not in s, "No spaces")
]
username_widget = InquirerText("Username:", validators=DEFAULT_VALIDATORS)from textual.validation import Validator, ValidationResult
class MinLength(Validator):
def __init__(self, length: int):
super().__init__()
self.length = length
def validate(self, value: str) -> ValidationResult:
return self.success() if len(value) >= self.length else self.failure(f"Min {self.length} chars")
class NoSpaces(Validator):
def validate(self, value: str) -> ValidationResult:
return self.success() if ' ' not in value else self.failure("No spaces allowed")
widget = InquirerText("Username:", validators=[MinLength(3), NoSpaces()])def build_form_widgets(field_defs):
"""Build widgets from field definitions."""
widgets = []
for field in field_defs:
if field['type'] == 'text':
widgets.append(InquirerText(field['label']))
elif field['type'] == 'number':
widgets.append(InquirerNumber(field['label']))
elif field['type'] == 'confirm':
widgets.append(InquirerConfirm(field['label'], default=field.get('default', False)))
elif field['type'] == 'select':
widgets.append(InquirerSelect(field['label'], field['choices']))
return widgets
field_definitions = [
{'type': 'text', 'label': 'Name:'},
{'type': 'number', 'label': 'Age:'},
{'type': 'confirm', 'label': 'Active:', 'default': True}
]
widgets = build_form_widgets(field_definitions)
result = prompts.multi(widgets)widget = InquirerText("Enter name:", default="John", validators=[...])
# Accessible attributes:
widget.message # str: Prompt message
widget.default # str: Default value
widget.validators # Validator | Iterable[Validator] | None
widget.input # Input | None: Underlying Textual Input widget
widget.mandatory # bool: Whether response required (inherited from base)widget = InquirerSecret("Password:")
# Accessible attributes:
widget.message # str: Prompt message
widget.input # Input | None: Underlying Textual Input (password type)
widget.mandatory # bool: Whether response requiredwidget = InquirerNumber("Age:")
# Accessible attributes:
widget.message # str: Prompt message
widget.input # Input | None: Underlying Textual Input (integer type)
widget.mandatory # bool: Whether response requiredwidget = InquirerConfirm("Continue?", confirm_character='y', reject_character='n', default=True)
# Accessible attributes:
widget.message # str: Prompt message
widget.default # bool: Default value
widget.value # bool: Current value
widget.label # Label: Textual Label widget showing (Y/n) or (y/N)
widget.mandatory # bool: Whether response required
# Note: confirm_character and reject_character are constructor parameters only, not accessible as attributesfrom inquirer_textual.common.Choice import Choice
widget = InquirerSelect("Pick:", [Choice("A"), "B", "C"], default="A", mandatory=True)
# Accessible attributes:
widget.message # str: Prompt message
widget.choices # list[str | Choice]: List of choices
widget.default # str | Choice | None: Default selection
widget.list_view # ListView | None: Underlying Textual ListView
widget.selected_label # ChoiceLabel | None: Currently selected label widget
widget.selected_item # str | Choice | None: Currently selected item
widget.mandatory # bool: Whether selection requiredwidget = InquirerCheckbox("Select:", ["A", "B", "C"], enabled=["A"])
# Accessible attributes:
widget.message # str: Prompt message
widget.choices # list[str | Choice]: List of choices
widget.enabled # list[str | Choice] | None: Pre-enabled items (not functional in v0.2.0)
widget.list_view # ListView | None: Underlying Textual ListView
widget.selected_label # ChoiceCheckboxLabel | None: Currently highlighted label
widget.selected_item # str | Choice | None: Currently highlighted item
widget.mandatory # bool: Always True for checkboxmulti = InquirerMulti([InquirerText("A:"), InquirerText("B:")])
# Accessible attributes:
multi.widgets # list[InquirerWidget]: List of widgets in sequence
multi.mandatory # bool: Whether response requiredAvailable on: InquirerText, InquirerSecret, InquirerNumber, InquirerSelect, InquirerCheckbox
# Focus the widget's input/selection area
widget = InquirerText("Input:")
# Basic focus
widget.focus()
# Focus without scrolling into view
widget.focus(scroll_visible=False)
# Chainable
widget.focus().update_label("New message")Available on: All InquirerWidget subclasses
# Get current value without submitting
text_widget = InquirerText("Name:", default="Alice")
current = text_widget.current_value() # Returns: "Alice"
select_widget = InquirerSelect("Pick:", ["A", "B", "C"])
current = select_widget.current_value() # Returns: "A" (first item or default)
confirm_widget = InquirerConfirm("OK?", default=True)
current = confirm_widget.current_value() # Returns: True
checkbox_widget = InquirerCheckbox("Select:", ["X", "Y"])
current = checkbox_widget.current_value() # Returns: [] (empty list initially)
number_widget = InquirerNumber("Port:")
current = number_widget.current_value() # Returns: "" (empty string initially)
secret_widget = InquirerSecret("Password:")
current = secret_widget.current_value() # Returns: "" (empty string initially)Available on: InquirerCheckbox only
checkbox_widget = InquirerCheckbox("Select:", ["A", "B", "C"])
# Manually toggle the currently highlighted item
# This is automatically bound to spacebar
checkbox_widget.action_toggle_selected()
# Use case: Programmatic checkbox toggling
def flow(app):
widget = InquirerCheckbox("Items:", ["1", "2", "3"])
# Could theoretically programmatically toggle items here
result = app.prompt(widget)def stateful_flow(app):
"""Access widget state during flow."""
select_widget = InquirerSelect("Choose:", ["Option A", "Option B", "Option C"], default="Option B")
# Before prompting, check initial state
initial = select_widget.current_value()
print(f"Initial selection: {initial}") # "Option B"
result = app.prompt(select_widget)
if result.command == 'select':
# After prompting, state reflects user choice
final = select_widget.current_value()
print(f"Final selection: {final}") # User's selection
print(f"Match: {final == result.value}") # True
app.stop(result.value)def reusable_widget_flow(app):
"""Reuse widget instances across prompts."""
# Create reusable widgets
name_widget = InquirerText("Name:", default="")
confirm_widget = InquirerConfirm("Is this correct?", default=True)
while True:
name_result = app.prompt(name_widget)
if name_result.command != 'select':
return
confirm_result = app.prompt(confirm_widget)
if confirm_result.value:
app.stop(name_result.value)
return
# Loop continues, widgets can be reusedfrom textual.validation import Validator, ValidationResult, Function
class RangeValidator(Validator):
def __init__(self, min_val: int, max_val: int):
super().__init__()
self.min = min_val
self.max = max_val
def validate(self, value: str) -> ValidationResult:
try:
num = int(value)
if self.min <= num <= self.max:
return self.success()
return self.failure(f"Must be {self.min}-{self.max}")
except ValueError:
return self.failure("Must be a number")
def create_age_widget(label="Age:", min_age=0, max_age=120):
"""Factory for age input widgets."""
return InquirerText(
label,
validators=RangeValidator(min_age, max_age)
)
# Usage
child_age_widget = create_age_widget("Child age:", min_age=0, max_age=17)
adult_age_widget = create_age_widget("Adult age:", min_age=18, max_age=120)def conditional_widget_flow(app):
"""Create different widgets based on conditions."""
user_type = app.prompt(InquirerSelect("User type:", ["Basic", "Advanced"]))
if user_type.value == "Advanced":
# Advanced users get more options
widget = InquirerCheckbox(
"Advanced features:",
["API Access", "Custom Scripts", "Admin Panel", "Beta Features"]
)
else:
# Basic users get simple confirmation
widget = InquirerConfirm("Enable notifications?", default=True)
result = app.prompt(widget)
app.stop({
'user_type': user_type.value,
'features': result.value
})def composed_form_flow(app):
"""Compose complex forms from widget building blocks."""
# Define form sections
personal_info = [
InquirerText("First name:"),
InquirerText("Last name:"),
InquirerText("Email:")
]
preferences = [
InquirerSelect("Theme:", ["Light", "Dark", "Auto"]),
InquirerConfirm("Enable notifications:", default=True),
InquirerCheckbox("Newsletter topics:", ["Tech", "Product Updates", "Events"])
]
# Collect personal info
personal_data = []
for widget in personal_info:
result = app.prompt(widget)
if result.command != 'select':
return
personal_data.append(result.value)
# Collect preferences
preference_data = []
for widget in preferences:
result = app.prompt(widget)
if result.command != 'select':
return
preference_data.append(result.value)
app.stop({
'personal': personal_data,
'preferences': preference_data
})def inspect_widget_state_flow(app):
"""Inspect and use widget state during flow."""
checkbox_widget = InquirerCheckbox(
"Select items:",
["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]
)
result = app.prompt(checkbox_widget)
if result.command == 'select':
selected_count = len(result.value)
total_count = len(checkbox_widget.choices)
print(f"Selected {selected_count} of {total_count} items")
# Access widget attributes
print(f"Message was: {checkbox_widget.message}")
print(f"Available choices: {checkbox_widget.choices}")
app.stop(result.value)class ValidatedTextWidget:
"""Wrapper for InquirerText with built-in validation."""
def __init__(self, message: str, validator_func, error_message: str):
self.message = message
self.validator = Function(validator_func, error_message)
self.widget = InquirerText(message, validators=self.validator)
def prompt(self, app):
"""Prompt with automatic validation."""
return app.prompt(self.widget)
# Usage
email_widget = ValidatedTextWidget(
"Email:",
lambda s: '@' in s and '.' in s,
"Invalid email format"
)
def flow(app):
result = email_widget.prompt(app)
if result.command == 'select':
app.stop(result.value)def navigable_wizard_flow(app):
"""Wizard with forward/backward navigation."""
from inquirer_textual.common.Choice import Choice
steps = [
("Personal Info", InquirerText("Name:")),
("Contact", InquirerText("Email:")),
("Preferences", InquirerSelect("Theme:", ["Light", "Dark"])),
("Confirmation", InquirerConfirm("Submit?", default=True))
]
current_step = 0
results = {}
while current_step < len(steps):
step_name, widget = steps[current_step]
# Add navigation options for non-first steps
if current_step > 0:
go_back = app.prompt(InquirerConfirm(f"[Step {current_step + 1}] {step_name} (Go back?)", default=False))
if go_back.value:
current_step -= 1
continue
result = app.prompt(widget)
if result.command != 'select':
return # User quit
results[step_name] = result.value
current_step += 1
app.stop(results)def dynamic_choices_flow(app):
"""Generate choices dynamically based on previous input."""
from inquirer_textual.common.Choice import Choice
# First prompt: Select category
category_result = app.prompt(InquirerSelect(
"Category:",
["Programming", "Design", "Marketing"]
))
if category_result.command != 'select':
return
# Generate subcategories based on category
subcategories = {
"Programming": ["Python", "JavaScript", "Go", "Rust"],
"Design": ["UI/UX", "Graphic Design", "3D Modeling"],
"Marketing": ["SEO", "Content Marketing", "Social Media"]
}
subchoices = subcategories.get(category_result.value, [])
subchoices_with_data = [
Choice(sub, data={'category': category_result.value, 'sub': sub})
for sub in subchoices
]
# Second prompt: Select subcategory
sub_result = app.prompt(InquirerSelect(
f"Select {category_result.value} subcategory:",
subchoices_with_data
))
if sub_result.command == 'select':
app.stop(sub_result.value.data)from typing import Optional
from inquirer_textual.InquirerApp import InquirerApp
def create_text_prompt(message: str, default: str = '') -> InquirerText:
"""Factory with type hints for clarity."""
return InquirerText(message, default=default)
def flow(app: InquirerApp) -> None:
widget: InquirerText = create_text_prompt("Name:")
result = app.prompt(widget)
if result.command == 'select':
value: str = result.value
app.stop(value)def create_registration_widgets():
"""Separate widget creation from flow logic."""
return [
InquirerText("Username:", validators=Function(lambda s: len(s) >= 3, "Min 3 chars")),
InquirerSecret("Password:"),
InquirerText("Email:", validators=Function(lambda s: '@' in s, "Invalid email")),
InquirerConfirm("Agree to terms?", default=False)
]
def registration_flow(app):
widgets = create_registration_widgets()
results = []
for widget in widgets:
result = app.prompt(widget)
if result.command != 'select':
return
results.append(result.value)
username, password, email, agreed = results
if not agreed:
app.prompt(InquirerText("Must agree to terms. Press Enter."))
return
app.stop({'username': username, 'email': email})def validated_flow(app):
"""Always validate results even after widget validation."""
age_widget = InquirerNumber("Age:")
age_result = app.prompt(age_widget)
if age_result.command != 'select':
return
# Widget returns string, convert and validate
try:
age = int(age_result.value)
if not (0 <= age <= 120):
app.prompt(InquirerText("Invalid age range. Press Enter."))
return
except ValueError:
app.prompt(InquirerText("Invalid number. Press Enter."))
return
app.stop(age)from inquirer_textual.InquirerApp import InquirerApp
def flow(app):
widget1 = InquirerText("First:")
result1 = app.prompt(widget1)
if result1.command == 'select':
widget2 = InquirerText("Second:")
result2 = app.prompt(widget2)
app.stop((result1.value, result2.value))
app = InquirerApp()
result = app.run(inline=True, inquiry_func=flow)from inquirer_textual import prompts
widgets = [
InquirerText("Name:"),
InquirerSecret("Password:"),
InquirerConfirm("Remember me?")
]
result = prompts.multi(widgets)
if result.command == 'select':
name, password, remember = result.valueapp = InquirerApp()
app.widget = InquirerText("Name:")
result = app.run(inline=True)__init__()widget = InquirerText("Name:", default="Alice")widget.default = "Bob" # Can modify before mountMounting: Widget added to app DOM
on_mount() called internallyFocus: Widget receives input focus
widget.focus() # or app.focus_widget()Interaction: User types/selects
current_value() reflects current stateSubmission: User presses Enter
Submit messageMessage Handling: App processes Submit
Cleanup: Widget unmounted (if flow continues)
[Created] → [Mounted] → [Focused] → [Active] → [Submitted] → [Unmounted]
↑ ↓ ↑ ↓
└────────────┴───────────┴────────────┘
(Can refocus and reuse)# LESS EFFICIENT: Creating widgets inside loop
def inefficient_flow(app):
for i in range(10):
# Creates new widget each iteration
result = app.prompt(InquirerText(f"Item {i}:"))
if result.command != 'select':
return
# MORE EFFICIENT: Reuse widget if possible
def efficient_flow(app):
widget = InquirerText("Enter item:")
for i in range(10):
# Update message programmatically if needed
result = app.prompt(widget)
if result.command != 'select':
return# For very large choice lists (1000+ items), consider:
# 1. Filtering/search before presenting
def filtered_select_flow(app):
all_items = load_huge_dataset() # 10000 items
# Ask for filter first
filter_result = app.prompt(InquirerText("Search:"))
if filter_result.command != 'select':
return
# Filter items
filtered = [item for item in all_items if filter_result.value.lower() in item.lower()]
# Present filtered list
select_result = app.prompt(InquirerSelect("Choose:", filtered[:100])) # Limit to 100
if select_result.command == 'select':
app.stop(select_result.value)
# 2. Pagination (see advanced-app.md for example)
# 3. Hierarchical selection (category then item)