ButtonGroup#

A row (or column) of visually-connected buttons sharing a common accent and variant. Buttons are added one at a time via add().

ButtonGroup — light theme ButtonGroup — dark theme

Usage#

Think of a ButtonGroup as a set of related actions with one shared style and one shared click handler: each button carries a key, and on_click() on the group reports which key was pressed — so you handle the whole cluster in one place instead of wiring each button separately. Reach for ToggleGroup instead when the buttons represent a selection to remember rather than actions to fire.

Basic usage#

Create a group and add buttons with add(). The group handles layout and shared styling automatically.

bg = bs.ButtonGroup()
bg.add("Save")
bg.add("Cancel")
bg.add("Reset")

Accent colors#

Set accent= on the group to apply a color intent to every button. Each button inherits the group accent unless you override it individually via **kwargs in add().

for accent in ("default", "primary", "secondary", "success", "warning", "danger"):
    bg = bs.ButtonGroup(accent=accent)
    bg.add("Save")
    bg.add("Cancel")
ButtonGroup accent colors — light theme ButtonGroup accent colors — dark theme

Style variants#

Use variant= to control visual weight across the whole group.

for variant in ("solid", "outline", "ghost"):
    bg = bs.ButtonGroup(accent="primary", variant=variant)
    bg.add("Save")
    bg.add("Cancel")
    bg.add("Reset")
ButtonGroup style variants — light theme ButtonGroup style variants — dark theme

Icons#

Pass icon= to add() to show a Bootstrap Icon alongside the label.

bg = bs.ButtonGroup(accent="primary", variant="outline")
bg.add("Bold",      icon="type-bold")
bg.add("Italic",    icon="type-italic")
bg.add("Underline", icon="type-underline")
ButtonGroup icons — light theme ButtonGroup icons — dark theme

Icon-only#

Omit the label to show only the icon — icon_only is inferred automatically, same as for Button.

bg1 = bs.ButtonGroup(variant="outline", accent="primary")
bg1.add(icon="type-bold")
bg1.add(icon="type-italic")
bg1.add(icon="type-underline")
bg1.add(icon="type-strikethrough")

bg2 = bs.ButtonGroup()
bg2.add(icon="scissors")
bg2.add(icon="copy")
bg2.add(icon="clipboard")
ButtonGroup icon-only — light theme ButtonGroup icon-only — dark theme

Vertical orientation#

Pass 'vertical' as the first argument (or orient='vertical') to stack buttons top-to-bottom instead of left-to-right.

bg = bs.ButtonGroup("vertical", accent="primary", variant="outline")
bg.add("Cut",   icon="scissors")
bg.add("Copy",  icon="copy")
bg.add("Paste", icon="clipboard")
ButtonGroup vertical — light theme ButtonGroup vertical — dark theme

Compact density#

Use density='compact' to reduce button padding — useful inside toolbars.

bg1 = bs.ButtonGroup(accent="primary")
bg1.add("Cut",   icon="scissors")
bg1.add("Copy",  icon="copy")
bg1.add("Paste", icon="clipboard")

bg2 = bs.ButtonGroup(accent="primary", density="compact")
bg2.add("Cut",   icon="scissors")
bg2.add("Copy",  icon="copy")
bg2.add("Paste", icon="clipboard")
ButtonGroup compact density — light theme ButtonGroup compact density — dark theme

Disabled state#

Set disabled=True to make all buttons in the group non-interactive at once. Toggle the disabled property at runtime to re-enable.

bg1 = bs.ButtonGroup(accent="primary")
bg1.add("Save")
bg1.add("Cancel")

bg2 = bs.ButtonGroup(accent="primary", disabled=True)
bg2.add("Save")
bg2.add("Cancel")

# Toggle at runtime
bg2.disabled = False
ButtonGroup disabled — light theme ButtonGroup disabled — dark theme

Events#

Use on_click() on the group to handle any button press in one place — the handler receives a ButtonGroupClickEvent with the key, text, and icon of the button that fired. A disabled button never fires.

bg = bs.ButtonGroup(accent="primary")
bg.add("Save",   icon="save",  key="save")
bg.add("Cancel", icon="x-lg",  key="cancel")
bg.add("Delete", icon="trash", key="delete")

def handle_click(e):
    print(e.key, e.text, e.icon)

# Subscription (cancellable)
sub = bg.on_click(handle_click)
sub.cancel()

# Stream (composable) — e.g. only the "delete" key
bg.on_click().filter(lambda e: e.key == "delete").listen(handle_click)

Note

See Handling Actions for routing actions across a screen and Events for the event model behind on_click.

Keyboard#

  • Tab / Shift+Tab — move focus between the buttons in the group (disabled buttons are skipped).

  • Space / Return — activate the focused button, firing on_click exactly as a mouse click does.

Managing items#

add() and add_all() return the key(s) assigned to each button. Keys are auto-generated ('widget_0', 'widget_1', …) unless you provide them explicitly. Use keys with remove(), update_item(), query_item(), and item().

bg = bs.ButtonGroup(accent="primary")

# Add one at a time
bg.add("Save",   icon="save",  key="save")
bg.add("Cancel", icon="x-lg", key="cancel")

# Or all at once
bg.add_all([
    dict(label="Cut",   icon="scissors", key="cut"),
    dict(label="Copy",  icon="copy",     key="copy"),
    dict(label="Paste", icon="clipboard"),
])

# Inspect
print(bg.keys)         # ('save', 'cancel', 'cut', 'copy', 'widget_0')
print(len(bg))         # 5
print("save" in bg)    # True

# Query a button's current value
bg.query_item("save", "text")          # 'Save'

# Reconfigure after creation
bg.update_item("save", text="Saving…", state="disabled")

# Remove a button
bg.remove("cancel")

# Access the underlying button widget
btn = bg.item("save")

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#

  • Button — standalone button

  • ToggleGroup — button group with selection state tracking

API#

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

ButtonGroup

A row (or column) of visually-connected buttons sharing accent and variant.

Full Example#

 1
 2with bs.App(title="ButtonGroup Demo", padding=20, gap=16) as app:
 3
 4    # Accent colors
 5    bs.Label("Accent Colors", font="heading-sm")
 6    with bs.Row(gap=8):
 7        for accent in ("default", "primary", "secondary", "success", "warning", "danger"):
 8            bg = bs.ButtonGroup(accent=accent)
 9            bg.add("A")
10            bg.add("B")
11
12    # Style variants
13    bs.Label("Style Variants", font="heading-sm")
14    with bs.Row(gap=8):
15        for variant in ("solid", "outline", "ghost"):
16            bg = bs.ButtonGroup(variant=variant)
17            bg.add("Save")
18            bg.add("Cancel")
19
20    # Icons
21    bs.Label("Icons", font="heading-sm")
22    bg = bs.ButtonGroup(accent="primary", variant="outline")
23    bg.add("Bold",      icon="type-bold")
24    bg.add("Italic",    icon="type-italic")
25    bg.add("Underline", icon="type-underline")
26
27    # Icon-only (icon_only inferred when no label is provided)
28    bs.Label("Icon Only", font="heading-sm")
29    with bs.Row(gap=8):
30        bg1 = bs.ButtonGroup(variant="outline", accent="primary")
31        bg1.add(icon="type-bold")
32        bg1.add(icon="type-italic")
33        bg1.add(icon="type-underline")
34        bg1.add(icon="type-strikethrough")
35
36        bg2 = bs.ButtonGroup()
37        bg2.add(icon="scissors")
38        bg2.add(icon="copy")
39        bg2.add(icon="clipboard")
40
41    # Vertical orientation
42    bs.Label("Vertical Orientation", font="heading-sm")
43    bg = bs.ButtonGroup("vertical", accent="primary", variant="outline")
44    bg.add("Cut",   icon="scissors")
45    bg.add("Copy",  icon="copy")
46    bg.add("Paste", icon="clipboard")
47
48    # Compact density
49    bs.Label("Compact Density", font="heading-sm")
50    with bs.Row(gap=8):
51        bg1 = bs.ButtonGroup(accent="primary")
52        bg1.add("Cut",   icon="scissors")
53        bg1.add("Copy",  icon="copy")
54        bg1.add("Paste", icon="clipboard")
55
56        bg2 = bs.ButtonGroup(accent="primary", density="compact")
57        bg2.add("Cut",   icon="scissors")
58        bg2.add("Copy",  icon="copy")
59        bg2.add("Paste", icon="clipboard")
60
61    # Disabled
62    bs.Label("Disabled", font="heading-sm")
63    with bs.Row(gap=8):
64        bg1 = bs.ButtonGroup(accent="primary")
65        bg1.add("Save")
66        bg1.add("Cancel")
67
68        bg2 = bs.ButtonGroup(accent="primary", disabled=True)
69        bg2.add("Save")
70        bg2.add("Cancel")
71
72    # Handling clicks (group-level on_click — e.data has key, text, icon)
73    bs.Label("Handling Clicks", font="heading-sm")
74    bg = bs.ButtonGroup(accent="primary")
75    bg.add("Save",   icon="save",  key="save")
76    bg.add("Cancel", icon="x-lg", key="cancel")
77    bg.add("Delete", icon="trash", key="delete")
78    last = bs.Signal("(none)")
79    bg.on_click(lambda e: last.set(e.key))
80    bs.Label(textsignal=last, accent="secondary")
81
82app.run()