Toolbar#

A horizontal strip of buttons, labels, menus, and other widgets — the multi-purpose chrome primitive. Items are added left-to-right via add_button(), add_label(), add_menu(), add_divider(), add_spacer(), and add_widget(). A window’s top region is a stack of toolbars you build with add_toolbar() (see Toolbars in a window).

Toolbar demo — light theme Toolbar demo — dark theme

Usage#

Build a toolbar by adding items left to right — each add_* call appends one. add_spacer() splits the bar: items added before it stay on the left, and everything after it is pushed to the right edge. The same widget serves as a standalone strip and as the building block of a window’s chrome — a stack of toolbars (see Toolbars in a window).

Adding buttons#

Pass label= for text, icon= for an icon, or both for a text-and-icon button. Omit label= to get an icon-only button.

tb = bs.Toolbar(horizontal="stretch")

tb.add_button("Save", icon="floppy")           # text + icon
tb.add_button(icon="gear")                      # icon-only
tb.add_button("Cancel")                         # text-only

Use on_click= to attach a callback, and accent= to apply a color intent to individual buttons.

tb.add_button("Publish", icon="cloud-upload", accent="primary", on_click=publish)
tb.add_button("Preview", icon="eye")
tb.add_button("Draft",   icon="pencil")
tb.add_spacer()
tb.add_button("Discard", icon="trash", accent="danger", on_click=discard)
Toolbar accent buttons — light theme Toolbar accent buttons — dark theme

Dividers and spacers#

add_divider() inserts a thin vertical rule between item groups. add_spacer() inserts a flexible gap that pushes everything added after it to the right side.

tb.add_button("Bold",   icon="type-bold")
tb.add_button("Italic", icon="type-italic")
tb.add_divider()
tb.add_button("Align left",   icon="text-left")
tb.add_button("Align center", icon="text-center")
tb.add_button("Align right",  icon="text-right")
tb.add_spacer()
tb.add_button(icon="gear")          # pinned to the right
Toolbar separators and spacers — light theme Toolbar separators and spacers — dark theme

Labels#

add_label() adds non-interactive text, optionally with an icon. Use it for the application name or section titles.

tb.add_label("My App", font="heading-md")
tb.add_divider()
tb.add_button("New", icon="file-earmark-plus")

Controlling items after creation#

add_button() returns the Button and add_label() returns the Label, so you can keep a reference and drive it later with the widget’s live properties — no need to rebuild the bar.

save = tb.add_button("Save", icon="floppy", on_click=save_doc)
status = tb.add_label("Ready")

save.disabled = True              # disable until there are changes
save.text = "Saving…"             # update the label text
status.text = "All changes saved"

The bar applies its own density and surface to the items it builds, so a returned button or label already matches the rest of the toolbar.

Adding menus#

A menu (File / Edit / …) is just another toolbar item. add_menu() returns a context-manager builder — add add_action / add_check / add_radio / add_divider items inside the with block. A shortcut= is shown beside the item and bound for you. On Windows/Linux the menu renders as an in-window dropdown; on macOS it bridges to the native global menu bar.

with tb.add_menu("File") as file:
    file.add_action("New",  shortcut="Mod+N", on_click=new_doc)
    file.add_action("Open", shortcut="Mod+O", on_click=open_doc)
    file.add_divider()
    file.add_action("Quit", shortcut="Mod+Q", on_click=app.close)
with tb.add_menu("View") as view:
    view.add_check("Status bar", checked=True, on_click=toggle_status)

Menus and command buttons can share one toolbar — that is the whole point of the unified toolbar.

Density#

The Toolbar is multi-purpose, so the right density depends on the role:

  • "default" (the standalone bs.Toolbar default) — roomier buttons and padding. Use it for a primary command bar where the buttons are the focus.

  • "compact" — a tight strip. Use it for window chrome (menu bars, title bars), secondary bars (a rich-text formatting strip), or anywhere vertical space is tight. Items packed on it — buttons, menu triggers, the theme toggle — all follow the bar’s density automatically.

Window chrome added with add_toolbar() defaults to "compact" (window bars read as tight strips); pass density="default" for a roomier one. A standalone bs.Toolbar defaults to "default".

tb = bs.Toolbar(horizontal="stretch", density="compact")
tb.add_button(icon="type-bold")
tb.add_button(icon="type-italic")
tb.add_button(icon="type-underline")
tb.add_divider()
tb.add_button(icon="text-left")
tb.add_button(icon="text-center")
tb.add_button(icon="text-right")
tb.add_divider()
tb.add_button(icon="list-ul")
tb.add_button(icon="list-ol")
tb.add_spacer()
tb.add_button(icon="arrow-counterclockwise")
tb.add_button(icon="arrow-clockwise")
Toolbar compact density — light theme Toolbar compact density — dark theme

Button variant#

button_variant= sets the default variant for all buttons added to the toolbar. Override per-button with the variant= argument on add_button().

tb = bs.Toolbar(horizontal="stretch", button_variant="outline")
tb.add_button("Save", icon="floppy")        # outline
tb.add_button("Run", variant="solid")       # override to solid

Border and surface#

show_border=True draws a border around the toolbar. surface= controls the background token — 'card' lifts it slightly from the page background.

tb = bs.Toolbar(horizontal="stretch", show_border=True, surface="card")
tb.add_button("New",  icon="file-earmark-plus")
tb.add_button("Open", icon="folder2-open")
tb.add_button("Save", icon="floppy")
tb.add_spacer()
tb.add_button(icon="gear")
Toolbar with border and card surface — light theme Toolbar with border and card surface — dark theme

Custom widgets#

Use add_widget() to embed any widget (a Select, a TextField, …). Pass the widget class and let the toolbar build it — it applies the bar’s density and surface (for any the class accepts), so the widget matches the rest of the bar:

tb = bs.Toolbar(horizontal="stretch", density="compact")
tb.add_widget(bs.Select, options=["main", "dev", "feat/new-ui"], value="main")
tb.add_widget(bs.TextField, placeholder="Search", width=24)

To add a widget you have already built yourself, parent it onto the bar directly — bs.MyCustomWidget(parent=tb) — and it attaches automatically, keeping whatever you configured.

Toolbars in a window#

A window’s top chrome is a stack of toolbars, added with app.add_toolbar() (the same on Window and AppShell). Each call stacks a new full-width band, top to bottom; divider=True draws a hairline beneath a band. The returned Toolbar is a scoping context manager — with app.add_toolbar() as bar: reads naturally and hands back the handle, but you fill the bar only through its add_* methods (so each item inherits the bar’s density and surface).

with bs.AppShell(title="My App") as shell:
    # A menu row.
    with shell.add_toolbar() as menus:
        with menus.add_menu("File") as file:
            file.add_action("Quit", shortcut="Mod+Q", on_click=shell.close)
        with menus.add_menu("View") as view:
            view.add_action("Refresh", shortcut="Mod+R", on_click=refresh)
        menus.add_spacer()
        menus.add_theme_toggle()
    # A command row beneath it, separated by a hairline.
    with shell.add_toolbar(divider=True) as commands:
        commands.add_button("Run", icon="play", accent="primary", on_click=run)

Window chrome defaults to surface='chrome' and density='compact'; override either per call.

Window controls#

show_window_controls=True adds minimize / maximize / close at the right edge and lets the toolbar drag the window (double-click maximizes). This is how you build the title bar of an undecorated window — see the Undecorated window section on the App page for the full example and screenshot.

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#

AppShell — full application scaffold with a built-in toolbar, sidebar, and page stack.

Button — standalone button widget.

API#

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

Toolbar

A horizontal strip of buttons, labels, and other widgets.

Full Example#

 1
 2with bs.App(title="Toolbar demo", minsize=(700, 300), padding=16) as app:
 3
 4    with bs.Column(grow=True, horizontal="stretch", horizontal_items="stretch", gap=16):
 5
 6        # ── Default density ────────────────────────────────────────────────
 7        with bs.Column(horizontal_items="stretch", padding=(16, 12, 16, 4)):
 8            bs.Label("Default", font="heading-sm")
 9
10            tb1 = bs.Toolbar(horizontal="stretch")
11            tb1.add_label("Editor", font="heading-md")
12            tb1.add_divider()
13            tb1.add_button("New", icon="file-earmark-plus")
14            tb1.add_button("Open", icon="folder2-open")
15            tb1.add_button("Save", icon="floppy")
16            tb1.add_spacer()
17            tb1.add_theme_toggle()
18            tb1.add_button(icon="gear")
19
20        # ── Compact density ────────────────────────────────────────────────
21        with bs.Column(horizontal_items="stretch", padding=(16, 12, 16, 4)):
22            bs.Label("Compact density", font="heading-sm")
23
24            tb2 = bs.Toolbar(horizontal="stretch", density="compact")
25            tb2.add_button(icon="type-bold")
26            tb2.add_button(icon="type-italic")
27            tb2.add_button(icon="type-underline")
28            tb2.add_divider()
29            tb2.add_button(icon="text-left")
30            tb2.add_button(icon="text-center")
31            tb2.add_button(icon="text-right")
32            tb2.add_divider()
33            tb2.add_button(icon="list-ul")
34            tb2.add_button(icon="list-ol")
35            tb2.add_spacer()
36            tb2.add_button(icon="arrow-counterclockwise")
37            tb2.add_button(icon="arrow-clockwise")
38
39        # ── Accents and variants ───────────────────────────────────────────
40        with bs.Column(horizontal_items="stretch", padding=(16, 12, 16, 4)):
41            bs.Label("Accents and variants", font="heading-sm")
42            tb3 = bs.Toolbar(horizontal="stretch")
43            tb3.add_button("Publish", icon="cloud-upload", accent="primary")
44            tb3.add_button("Preview", icon="eye")
45            tb3.add_button("Draft", icon="pencil")
46            tb3.add_spacer()
47            tb3.add_button("Discard", icon="trash", accent="danger")
48
49        # ── Border & Surface ────────────────────────────────────────────────
50        with bs.Column(horizontal_items="stretch", padding=(16, 12, 16, 4)):
51            bs.Label("Border and Surface", font="heading-sm")
52
53            tb1 = bs.Toolbar(horizontal="stretch", show_border=True, surface="card")
54            tb1.add_button("New", icon="file-earmark-plus")
55            tb1.add_button("Open", icon="folder2-open")
56            tb1.add_button("Save", icon="floppy")
57
58
59app.run()