Button#

A clickable action trigger. Accepts the button text as the first positional argument.

Button — light theme Button — dark theme

Usage#

A button does one thing: it runs an action when activated. Wire that action with on_click= and shape the rest with two independent knobs — accent= for intent (the semantic color) and variant= for weight (how much it stands out). Everything else here is a refinement of those three ideas.

Accent colors#

Use accent= to express intent. The button renders correctly across all themes without hard-coding any color.

bs.Button("Default")
bs.Button("Primary",   accent="primary")
bs.Button("Secondary", accent="secondary")
bs.Button("Info",      accent="info")
bs.Button("Success",   accent="success")
bs.Button("Warning",   accent="warning")
bs.Button("Danger",    accent="danger")
Button accent colors — light theme Button accent colors — dark theme

Style variants#

Use variant= to control visual weight. Useful for distinguishing primary actions from secondary ones.

bs.Button("Solid",   accent="primary", variant="solid")
bs.Button("Outline", accent="primary", variant="outline")
bs.Button("Ghost",   accent="primary", variant="ghost")
Button style variants — light theme Button style variants — dark theme

Note

A ghost or outline button looks right only when it matches the surface it sits on. On a non-default container (a Card, a darker chrome strip, an overlay) pass surface= at construction ('card', 'chrome', 'overlay', …) to match. It is a build-time setting, not a runtime property; a Toolbar sets it for you on the buttons it builds.

Icons#

Pass any Bootstrap Icons name to icon=. The icon appears to the left of the text by default.

bs.Button("Save",   icon="save")
bs.Button("Delete", icon="trash",    accent="danger")
bs.Button("Export", icon="download", accent="secondary", variant="outline")
Button icons — light theme Button icons — dark theme

Icon position#

Use icon_position= to control where the icon sits relative to the text. Defaults to 'left'.

bs.Button("Left",   icon="arrow-left",  icon_position="left")
bs.Button("Right",  icon="arrow-right", icon_position="right")
bs.Button("Top",    icon="arrow-up",    icon_position="top")
bs.Button("Bottom", icon="arrow-down",  icon_position="bottom")
Button icon position — light theme Button icon position — dark theme

Icon-only#

Omit the text to show only the icon — icon_only is inferred automatically. Pass icon_only=True explicitly when you want to be clear about intent.

# icon_only inferred — no text provided
bs.Button(icon="plus-lg",  accent="success")
bs.Button(icon="dash-lg",  accent="danger")
bs.Button(icon="pencil",   accent="secondary", variant="outline")
bs.Button(icon="trash",    accent="danger",    variant="outline")

# explicit form — equivalent, states intent clearly
bs.Button("Delete", icon="trash", icon_only=True, accent="danger")
Button icon-only — light theme Button icon-only — dark theme

Uniform width#

Use width= (in character units) to make a row of buttons the same width.

bs.Button("Save",   accent="primary", width=10)
bs.Button("Cancel",                   width=10)
bs.Button("Reset",  accent="danger",  width=10)
Button uniform width — light theme Button uniform width — dark theme

Compact density#

Use density='compact' to reduce padding — useful in toolbars where space is tight.

bs.Button("Cut",   icon="scissors",  density="compact")
bs.Button("Copy",  icon="copy",      density="compact")
bs.Button("Paste", icon="clipboard", density="compact")
Button compact density — light theme Button compact density — dark theme

Disabled state#

bs.Button("Disabled Solid",   accent="primary",                    disabled=True)
bs.Button("Disabled Outline", accent="primary", variant="outline", disabled=True)
Button disabled — light theme Button disabled — dark theme

Reactive text#

Bind a Signal[str] to textsignal= so the button text updates automatically.

running  = bs.Signal(False)
btn_text = bs.Signal("Start")

running.subscribe(lambda v: btn_text.set("Stop" if v else "Start"))

bs.Button(textsignal=btn_text, accent="primary",
          on_click=lambda: running.set(not running()))

# Or set directly via the .text property
btn = bs.Button("Start", accent="primary")
btn.text = "Stop"

Custom image#

For artwork beyond the Bootstrap Icons set — a logo, an embedded resource — pass an Image handle to image=. Reach for icon= for standard glyphs and image= for custom pictures.

from bootstack.images import Image

bs.Button("Open", image=Image.open("logo.png"))

The handle is also a live property — assigning button.image swaps the picture in place. See Picture and the Image reference for the full image API.

Events#

A button fires on activation — a mouse release over it, Space/Return while it has keyboard focus, or a programmatic click(). A disabled button never fires on any of those paths. The simplest form is the on_click= constructor callback, which takes no arguments:

bs.Button("Save",   accent="primary", on_click=handle_save)
bs.Button("Cancel", on_click=lambda: print("Cancelled"))

Use the on_click() method when you need a cancellable subscription or want to compose the event as a Stream. Its handler receives the activation Event:

# Cancellable subscription
btn = bs.Button("Save", accent="primary")
sub = btn.on_click(lambda event: handle_save())
sub.cancel()  # unsubscribe

# Composable Stream — e.g. ignore rapid double-clicks
btn.on_click().debounce(300).listen(lambda event: handle_save())

Note

Because keyboard and programmatic activations carry no pointer, treat the event’s position and modifier fields as unset — use on_click= (or ignore the argument) unless you specifically need a mouse-only gesture. See Handling Actions for wiring actions across widgets and Events for the full event model.

Keyboard#

  • Tab / Shift+Tab — move focus to or from the button.

  • Space / Return — activate the focused button, firing the click exactly as a mouse release does. A disabled button is skipped in the focus order.

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 Button lives on the Widgets API page. At a glance:

Button

A clickable action trigger.

Full Example#

 1
 2with bs.App(title="Button 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", "info", "success", "warning", "danger"):
 8            bs.Button(accent.title(), accent=accent)
 9
10    # Style variants
11    bs.Label("Style Variants", font="heading-sm")
12    with bs.Row(gap=8):
13        bs.Button("Solid",   accent="primary", variant="solid")
14        bs.Button("Outline", accent="primary", variant="outline")
15        bs.Button("Ghost",   accent="primary", variant="ghost")
16
17    # Icons
18    bs.Label("Icons", font="heading-sm")
19    with bs.Row(gap=8):
20        bs.Button("Save",   icon="save")
21        bs.Button("Delete", icon="trash",    accent="danger")
22        bs.Button("Export", icon="download", accent="secondary", variant="outline")
23
24    # Icon position
25    bs.Label("Icon Position", font="heading-sm")
26    with bs.Row(gap=8):
27        bs.Button("Left",   icon="arrow-left",  icon_position="left")
28        bs.Button("Right",  icon="arrow-right", icon_position="right")
29        bs.Button("Top",    icon="arrow-up",    icon_position="top")
30        bs.Button("Bottom", icon="arrow-down",  icon_position="bottom")
31
32    # Icon-only (icon_only inferred when no text is provided)
33    bs.Label("Icon Only", font="heading-sm")
34    with bs.Row(gap=4):
35        bs.Button(icon="plus-lg",  accent="success")
36        bs.Button(icon="dash-lg",  accent="danger")
37        bs.Button(icon="pencil",   accent="secondary", variant="outline")
38        bs.Button(icon="trash",    accent="danger",    variant="outline")
39
40    # Uniform width
41    bs.Label("Uniform Width", font="heading-sm")
42    with bs.Row(gap=8):
43        bs.Button("Save",   accent="primary", width=10)
44        bs.Button("Cancel",                  width=10)
45        bs.Button("Reset",  accent="danger",  width=10)
46
47    # Reactive text via Signal
48    bs.Label("Reactive Text", font="heading-sm")
49    with bs.Row(gap=8, vertical_items="center"):
50        running  = bs.Signal(False)
51        btn_text = bs.Signal("Start")
52        running.subscribe(lambda v: btn_text.set("Stop" if v else "Start"))
53        bs.Button(
54            textsignal=btn_text,
55            accent="success",
56            on_click=lambda: running.set(not running()),
57        )
58        bs.Label(textsignal=btn_text, accent="secondary")
59
60    # Compact density
61    bs.Label("Compact Density", font="heading-sm")
62    with bs.Row(gap=4):
63        bs.Button("Cut",    icon="scissors",  density="compact")
64        bs.Button("Copy",   icon="copy",      density="compact")
65        bs.Button("Paste",  icon="clipboard", density="compact")
66
67    # Disabled
68    bs.Label("Disabled", font="heading-sm")
69    with bs.Row(gap=8):
70        bs.Button("Disabled Solid",   accent="primary", disabled=True)
71        bs.Button("Disabled Outline", accent="primary", variant="outline", disabled=True)
72
73app.run()