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.

TimeField — light theme TimeField — dark theme

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
TimeField formats — light theme TimeField formats — dark theme

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
TimeField validation — light theme TimeField validation — dark theme

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)
TimeField states — light theme TimeField states — dark theme

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.)

horizontal

Cross-axis placement of the widget: 'left', 'center', 'right', or 'stretch' to fill the available width.

grow

Claim and fill a share of the leftover vertical space (the layout direction). True or False.

margin

External spacing in pixels. Accepts an integer (equal on all sides), a 2-tuple (horizontal, vertical), or a 4-tuple (left, top, right, bottom).

margin_x

Horizontal external spacing (left and right). Accepts an integer or a 2-tuple (left, right) for asymmetric spacing. Overrides the horizontal component of margin=.

margin_y

Vertical external spacing (top and bottom). Accepts an integer or a 2-tuple (top, bottom) for asymmetric spacing. Overrides the vertical component of margin=.

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.)

vertical

Cross-axis placement of the widget: 'top', 'center', 'bottom', or 'stretch' to fill the available height.

grow

Claim and fill a share of the leftover horizontal space (the layout direction). True or False.

margin

External spacing in pixels. Accepts an integer (equal on all sides), a 2-tuple (horizontal, vertical), or a 4-tuple (left, top, right, bottom).

margin_x

Horizontal external spacing (left and right). Accepts an integer or a 2-tuple (left, right) for asymmetric spacing. Overrides the horizontal component of margin=.

margin_y

Vertical external spacing (top and bottom). Accepts an integer or a 2-tuple (top, bottom) for asymmetric spacing. Overrides the vertical component of margin=.

Grid

Used inside a Grid container.

row / column

Zero-based row and column indices.

rowspan / columnspan

Number of rows or columns to span.

horizontal

Horizontal placement within the grid cell: 'left', 'center', 'right', or 'stretch' to fill the cell width.

vertical

Vertical placement within the grid cell: 'top', 'center', 'bottom', or 'stretch' to fill the cell height.

margin

External spacing in pixels. Accepts an integer, a 2-tuple (horizontal, vertical), or a 4-tuple (left, top, right, bottom).

margin_x

Horizontal external spacing. Accepts an integer or (left, right).

margin_y

Vertical external spacing. Accepts an integer or (top, bottom).

See also#

API#

The complete reference for TimeField lives on the Widgets API page. At a glance:

TimeField

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()