SpinnerField#

A text-entry field with up/down spin buttons for stepping through a fixed list of values or a numeric range.

SpinnerField — light theme SpinnerField — dark theme

Usage#

Text mode#

Pass options= to step through a fixed list of strings. The spin buttons cycle forward and back through the list.

bs.SpinnerField(
    label="Priority",
    options=["Low", "Medium", "High", "Critical"],
    value="Medium",
)

Numeric mode#

Use min_value=, max_value=, and step= instead of options= for a numeric range. Only one mode should be used at a time.

bs.SpinnerField(
    label="Quantity",
    value=1,
    min_value=1,
    max_value=99,
    step=1,
)
SpinnerField modes — light theme SpinnerField modes — dark theme

Value formatting#

In numeric mode, value_format= displays the value with a locale-aware ICU pattern — the raw number is preserved internally; only the display changes. Requires localization to be enabled.

bs.SpinnerField(label="Price", value=10, min_value=0, max_value=100,
                value_format="currency")

Wrap around#

Set wrap=True to cycle back to the start when the end of the list (or range) is reached, and vice versa.

bs.SpinnerField(
    label="Month",
    options=["Jan", "Feb", "Mar", "Apr", "May", "Jun",
             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
    value="Jan",
    wrap=True,
)

Label and message#

Use label= for a field title and message= for helper text below.

bs.SpinnerField(
    label="Font size",
    value=12,
    min_value=6,
    max_value=72,
    step=2,
    message="Applies to the selected text.",
)

States#

bs.SpinnerField(value=5, min_value=1, max_value=10, label="Normal")
bs.SpinnerField(value=5, min_value=1, max_value=10, label="Read only", read_only=True)
bs.SpinnerField(value=5, min_value=1, max_value=10, label="Disabled",  disabled=True)
SpinnerField states — light theme SpinnerField states — dark theme

Reactive binding#

Bind a Signal[str] with textsignal=. The field and signal stay in sync automatically.

size = bs.Signal("M")
bs.SpinnerField(
    label="T-shirt size",
    options=["XS", "S", "M", "L", "XL", "XXL"],
    textsignal=size,
)
bs.Label(textsignal=size, accent="secondary")

Handling changes#

Use on_change() to respond when the value changes — whether from a spin button click, keyboard arrow key, or direct text entry.

sf = bs.SpinnerField(
    label="Rating",
    options=["★", "★★", "★★★", "★★★★", "★★★★★"],
    value="★★★",
    wrap=True,
)

def handle_change(e):
    print("Selected:", sf.value)

sf.on_change(handle_change)

# As a subscription (cancellable)
sub = sf.on_change(handle_change)
sub.cancel()

# As a Stream (composable)
sf.on_change().listen(handle_change)

Widget sizing#

All widgets accept self-placement kwargs via **kwargs. The parent container determines which options apply — stack-based parents use stack kwargs, grid-based parents use grid kwargs. Unrecognised keys are silently ignored.

Stack#

Used inside VStack, HStack, App, and other stack containers.

fill

Fill direction: 'x', 'y', 'both', or 'none'.

expand

Grow to consume extra space in the parent. True or False.

anchor

Alignment when the widget does not fill the available slot: 'n', 's', 'e', 'w', 'center', 'nw', etc.

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.

sticky

Alignment and fill within the grid cell. Any combination of 'n', 's', 'e', 'w' — e.g. 'ew' stretches horizontally, 'nsew' fills the entire cell.

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#

  • NumberField — numeric-only input with stepper buttons

  • Select — dropdown picker for longer lists

API#

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

SpinnerField

A text-entry field with spin buttons for stepping through values.

Full Example#

 1
 2with bs.App(title="SpinnerField Demo", padding=20, gap=16) as app:
 3
 4    # Text mode — fixed list of options
 5    bs.Label("Text Mode", font="heading-sm")
 6    bs.SpinnerField(
 7        label="Priority",
 8        options=["Low", "Medium", "High", "Critical"],
 9        value="Medium",
10    )
11
12    # Numeric mode — min / max / step
13    bs.Label("Numeric Mode", font="heading-sm")
14    bs.SpinnerField(
15        label="Quantity",
16        value=1,
17        min_value=1,
18        max_value=99,
19        step=1,
20    )
21
22    # Wrap-around
23    bs.Label("Wrap Around", font="heading-sm")
24    bs.SpinnerField(
25        label="Month",
26        options=["Jan", "Feb", "Mar", "Apr", "May", "Jun",
27                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
28        value="Jan",
29        wrap=True,
30    )
31
32    # Label and message
33    bs.Label("Label and Message", font="heading-sm")
34    bs.SpinnerField(
35        label="Font size",
36        value=12,
37        min_value=6,
38        max_value=72,
39        step=2,
40        message="Applies to the selected text.",
41    )
42
43    # Reactive binding
44    bs.Label("Reactive Binding", font="heading-sm")
45    size_sig = bs.Signal("Medium")
46    bs.SpinnerField(
47        label="T-shirt size",
48        options=["XS", "S", "M", "L", "XL", "XXL"],
49        value="M",
50        textsignal=size_sig,
51    )
52    bs.Label(textsignal=size_sig, accent="secondary")
53
54    # States
55    bs.Label("States", font="heading-sm")
56    bs.SpinnerField(value=5, min_value=1, max_value=10, label="Normal")
57    bs.SpinnerField(value=5, min_value=1, max_value=10, label="Read only", read_only=True)
58    bs.SpinnerField(value=5, min_value=1, max_value=10, label="Disabled",  disabled=True)
59
60    # Handling changes
61    bs.Label("Handling Changes", font="heading-sm")
62    last = bs.Signal("(none)")
63    sf = bs.SpinnerField(
64        label="Rating",
65        options=["★", "★★", "★★★", "★★★★", "★★★★★"],
66        value="★★★",
67        wrap=True,
68    )
69    sf.on_change(lambda e: last.set(sf.value))
70    bs.Label(textsignal=last, accent="secondary")
71
72app.run()