Getting Input#
bootstack has a field widget for every common kind of value — text, numbers,
dates, choices, on/off flags. They all share one idea: a field holds a value
you can read at any time, and you can bind that value to a Signal so it stays in sync with the rest of your app without manual
callbacks.
This guide covers reading and binding values. To check what the user typed, see Validation; to react to changes as they happen, see Handling Actions.
Text#
TextField is the single-line text input. Read the current
string from .value; seed it with a positional argument and add a
placeholder= for the empty hint:
name = bs.TextField("Ada", placeholder="Your name", label="Name")
print(name.value) # "Ada"
For longer text use TextArea, for hidden entry
PasswordField (with a built-in show/hide toggle), and for
source or config text CodeEditor. All three expose the same
.value string.
Numbers#
NumberField parses what the user types into an int or
float, clamps it to min_value / max_value, and steps by step with its
+/− buttons. .value is the parsed number (or None when the field is empty):
qty = bs.NumberField(value=1, min_value=1, max_value=99, step=1, label="Quantity")
price = bs.NumberField(value=9.99, min_value=0, step=0.5, label="Price")
Use an integer value/step for whole numbers and a float for fractional ones —
the field follows the type you give it.
Dates and times#
DateField and TimeField return real
datetime.date and datetime.time objects from .value, never strings. A date
field carries a calendar popup; a time field offers a dropdown of times. Both
accept constraints:
from datetime import date
when = bs.DateField(value=date.today(), min_date=date.today(), label="Ship date")
at = bs.TimeField(value=None, interval=15, label="Reminder")
print(when.value) # datetime.date(2026, 6, 14)
Choices#
For a single choice from a list, the widget depends on how much room you have and how many options there are:
RadioGroup— a few options, all visible at once.Select— a dropdown;searchable=Truefilters as you type.SelectButton— a compact button that opens a menu.
Options can be plain strings, or (label, value) tuples when the text shown
differs from the value you store. .value is always the value; .text is the
visible label:
size = bs.RadioGroup(["Small", "Medium", "Large"], value="Medium", title="Size")
theme = bs.Select(
options=[("Light theme", "light"), ("Dark theme", "dark")],
value="light",
label="Theme",
)
print(theme.value, theme.text) # "light" "Light theme"
For multiple choices, use ToggleGroup with mode="multi"
(its .value is a set), or a column of Checkbox widgets.
Sliders#
Slider picks a number on a track — handy when the rough
magnitude matters more than an exact figure. RangeSlider
picks a low/high pair:
volume = bs.Slider(value=50, min_value=0, max_value=100, show_value=True)
price_range = bs.RangeSlider(low_value=20, high_value=80, min_value=0, max_value=100)
print(volume.value) # 50.0
print(price_range.value) # (20.0, 80.0)
On/off#
Checkbox and Switch are boolean controls —
.value is True or False. A Switch reads as an immediate setting (Wi-Fi
on/off); a Checkbox reads as a selection you confirm later (terms accepted).
ToggleButton is the same idea styled as a pressable button.
notify = bs.Switch("Email notifications", value=True)
agree = bs.Checkbox("I accept the terms")
Binding values with signals#
Reading .value on demand is fine for a submit button. But when the same value
drives several widgets — a live preview, a summary label, a dependent control —
bind it to a Signal instead. Every widget bound to the same
signal updates together, in both directions.
Text-bearing fields use textsignal=; everything with a typed value uses
signal=:
with bs.App(title="Live preview", padding=16, gap=8) as app:
name = bs.Signal("World")
bs.TextField(textsignal=name, placeholder="Type a name…")
bs.Label(textsignal=name, font="heading-md", accent="primary")
app.run()
NumberField, DateField, and TimeField bind their typed value through
signal= — the signal holds an int/float, a date, or a time, not the
formatted text:
from datetime import date
count = bs.Signal(0)
bs.NumberField(signal=count, min_value=0)
bs.Label(textsignal=count.map(lambda n: f"{n} items")) # derived label
day = bs.Signal(date.today())
bs.DateField(signal=day)
Boolean and choice controls bind through signal= as well — a Checkbox to a
Signal(bool), a RadioGroup to a Signal holding the selected value. See
Signals for deriving and combining signals.
Note
When you pass both signal= and an initial value= to a boolean control,
the signal wins — seed the value on the signal itself
(bs.Signal(True)), not on the widget.
See also#
Composing Custom Fields — add icons, buttons, and toggles inside a field.
Signals — binding, deriving, and combining reactive values.
Validation — checking input against rules.
Handling Actions — reacting to changes and clicks.
Building Forms — collecting many fields at once.