TimeField#
A time input field with a searchable dropdown list of time intervals. The user can type a custom time or select from the dropdown.
Usage#
A time field reads and writes a real time through field.value — or
None when empty. The user types a time ('14:30' or '2:30 PM') or picks
one from the dropdown; value_format controls only how it is displayed, and
min_time / max_time limit which times the dropdown offers. The field
starts empty — pass value= to seed a starting time.
Basic usage#
bs.TimeField(label="Select a time")
Display formats#
The value_format= parameter controls how times are displayed in the field.
Any ICU time format pattern or named preset is accepted.
now = datetime.time(14, 30)
bs.TimeField(value=now, value_format="shortTime") # 2:30 PM
bs.TimeField(value=now, value_format="HH:mm") # 14:30
bs.TimeField(value=now, value_format="HH:mm:ss") # 14:30:00
Dropdown interval#
The interval= parameter sets the spacing between entries in the dropdown.
bs.TimeField(interval=15) # every 15 minutes
bs.TimeField(interval=30) # every 30 minutes (default)
bs.TimeField(interval=60) # hourly
Time range constraints#
Use min_time= and max_time= to limit which times appear in the dropdown.
import datetime
bs.TimeField(
label="Appointment time",
message="Available 9 AM – 5 PM.",
min_time=datetime.time(9, 0),
max_time=datetime.time(17, 0),
interval=30,
)
Reactive binding#
Pass a Signal via signal= to two-way bind the field’s time value to
application state. The signal carries the time object itself — not its text —
so reading it back gives you a time. Seed it with a time to set the
initial value.
from datetime import time
time_sig = bs.Signal(time(9, 0))
bs.TimeField(label="Pick a time", signal=time_sig)
# Derive a text signal for display
time_text = time_sig.map(lambda t: t.strftime("%I:%M %p") if t else "")
bs.Label(textsignal=time_text, accent="secondary")
Handling changes#
on_change() fires whenever the time value changes — whether by typing,
dropdown selection, or value= assignment.
tf = bs.TimeField(label="Start time")
tf.on_change(lambda e: print("Selected:", e.value))
Validation#
Attach rules with add_validation_rule(); they validate the field’s typed
value — a time, or None when empty. The range rule checks time
bounds with a message. This is distinct from min_time / max_time, which
limit the dropdown: a rule also catches an out-of-range time the user types,
and surfaces a message.
import datetime
field = bs.TimeField(label="Appointment")
field.add_validation_rule(
"range",
min=datetime.time(9, 0), max=datetime.time(17, 0),
message="Choose a time between 9 AM and 5 PM.",
trigger="blur",
)
is_valid = field.validate() # run every rule on demand
Validity is reactive state. field.valid is a Signal[bool] and
field.error a Signal[str] (the current message, "" when valid) — bind
the error straight to a label and it keeps itself in sync:
bs.Label(textsignal=field.error, accent="danger") # shows and clears itself
Note
The full rule taxonomy, the range rule, and aggregating a whole form’s
validity live in the Validation guide.
States#
bs.TimeField(value=now, label="Normal")
bs.TimeField(value=now, label="Read only", read_only=True)
bs.TimeField(value=now, label="Disabled", disabled=True)
Widget sizing#
All widgets accept self-placement kwargs via **kwargs. The parent
container determines which options apply — Column / Row parents use
the layout kwargs below, grid-based parents use grid kwargs.
Column (vertical layout)
Used inside a Column, App, or any other container with a column layout.
Children are arranged top-to-bottom, so horizontal aligns each child across
the width and grow shares the vertical space. (vertical does not apply —
the order of the children sets their top-to-bottom position.)
|
Cross-axis placement of the widget: |
|
Claim and fill a share of the leftover vertical space (the layout
direction). |
|
External spacing in pixels. Accepts an integer (equal on all
sides), a 2-tuple |
|
Horizontal external spacing (left and right). Accepts an integer
or a 2-tuple |
|
Vertical external spacing (top and bottom). Accepts an integer
or a 2-tuple |
Row (horizontal layout)
Used inside a Row or any other container with a row layout. Children are
arranged left-to-right, so vertical aligns each child across the height and
grow shares the horizontal space. (horizontal does not apply — the order
of the children sets their left-to-right position.)
|
Cross-axis placement of the widget: |
|
Claim and fill a share of the leftover horizontal space (the layout
direction). |
|
External spacing in pixels. Accepts an integer (equal on all
sides), a 2-tuple |
|
Horizontal external spacing (left and right). Accepts an integer
or a 2-tuple |
|
Vertical external spacing (top and bottom). Accepts an integer
or a 2-tuple |
Grid
Used inside a Grid container.
|
Zero-based row and column indices. |
|
Number of rows or columns to span. |
|
Horizontal placement within the grid cell: |
|
Vertical placement within the grid cell: |
|
External spacing in pixels. Accepts an integer, a 2-tuple
|
|
Horizontal external spacing. Accepts an integer or |
|
Vertical external spacing. Accepts an integer or |
See also#
DateField — date input with calendar picker
TextField — plain single-line text input
Validation — the full rule set and form-level validity
Customizing Fields — add buttons or icons inside a field
Signals — reactive binding for fields
API#
The complete reference for TimeField lives on the
Widgets API page. At a glance:
A time-input field with a searchable dropdown of time intervals. |
Full Example#
1
2with bs.App(title="TimeField Demo", padding=20, gap=16) as app:
3
4 # Basic usage
5 bs.Label("Basic", font="heading-sm")
6 bs.TimeField(label="Select a time")
7
8 # Time format presets
9 bs.Label("Display Formats", font="heading-sm")
10 now = datetime.time(14, 30)
11 bs.TimeField(value=now, label="Short time (default)", value_format="shortTime")
12 bs.TimeField(value=now, label="24-hour", value_format="HH:mm")
13 bs.TimeField(value=now, label="24-hour with seconds", value_format="HH:mm:ss")
14
15 # Dropdown interval
16 bs.Label("Dropdown Interval", font="heading-sm")
17 bs.TimeField(value=now, label="15-minute intervals", interval=15)
18 bs.TimeField(value=now, label="60-minute intervals", interval=60)
19
20 # Time range constraints
21 bs.Label("Business Hours", font="heading-sm")
22 bs.TimeField(
23 value=datetime.time(9, 0),
24 label="Appointment time",
25 message="Available Monday – Friday, 9 AM – 5 PM.",
26 min_time=datetime.time(9, 0),
27 max_time=datetime.time(17, 0),
28 interval=30,
29 )
30
31 # Reactive binding — the signal carries the time object
32 bs.Label("Reactive Binding", font="heading-sm")
33 time_sig = bs.Signal(now)
34 bs.TimeField(label="Pick a time", signal=time_sig)
35 time_text = time_sig.map(lambda t: t.strftime("%I:%M %p") if t else "")
36 bs.Label(textsignal=time_text, accent="secondary")
37
38 # States
39 bs.Label("States", font="heading-sm")
40 bs.TimeField(value=now, label="Normal")
41 bs.TimeField(value=now, label="Read only", read_only=True)
42 bs.TimeField(value=now, label="Disabled", disabled=True)
43
44 # Handling changes
45 bs.Label("Handling Changes", font="heading-sm")
46 last = bs.Signal("(none)")
47 tf = bs.TimeField(label="Choose a time")
48 tf.on_change(lambda e: last.set(str(tf.value)))
49 bs.Label(textsignal=last, accent="secondary")
50
51 # Validation
52 bs.Label("Validation", font="heading-sm")
53 validated = bs.TimeField(label="Required time", required=True)
54 with bs.Row(gap=8):
55 bs.Button("Validate", on_click=lambda: validated.validate())
56
57app.run()