Custom sidebar#

A bespoke sidebar you build by hand — a faceted filter panel, a tool palette, a layer list. The escape hatch for when the sidebar isn’t navigation at all, so none of the built-in providers fit.

Custom filter sidebar — light theme Custom filter sidebar — dark theme

How it works#

custom_nav() claims the sidebar as a blank container and returns it as a context manager — fill it with any widgets. You then drive the content area yourself through shell.content (also a container), typically by binding widgets to a Signal. Nothing is added or collapsed automatically — the sidebar is yours to fill and manage.

with shell.custom_nav():
    bs.Label("Filters", font="heading-md")
    bs.SelectButton(options=["All", "Electronics", "Home"], signal=category)

with shell.content:
    bs.Label("Results", font="heading-lg")
    bs.Label(textsignal=results)

Because there are no pages, navigate() does not apply — the content is whatever you put there and update.

Example#

 1"""Custom sidebar — a bespoke sidebar the providers can't express (search filters).
 2
 3``custom_nav()`` claims the sidebar as a blank container you fill with any widgets,
 4and you drive the content area yourself via ``shell.content``. A faceted filter
 5sidebar — category, price, rating — feeding a results area is the classic case:
 6it isn't navigation, so none of the nav providers fit. Reach for ``custom_nav()``
 7only when ``page_nav`` / ``list_nav`` / ``tree_nav`` cannot express your sidebar.
 8"""
 9import bootstack as bs
10
11PRODUCTS = [
12    {"name": "Wireless Mouse", "category": "Electronics", "price": 24},
13    {"name": "Desk Lamp", "category": "Home", "price": 39},
14    {"name": "Mechanical Keyboard", "category": "Electronics", "price": 89},
15    {"name": "Throw Pillow", "category": "Home", "price": 19},
16    {"name": "USB-C Hub", "category": "Electronics", "price": 45},
17]
18
19with bs.AppShell(title="Shop", size=(900, 580)) as shell:
20    with shell.add_toolbar() as bar:
21        with bar.add_menu("File") as file:
22            file.add_action("New order", shortcut="Mod+N", on_click=lambda: None)
23            file.add_divider()
24            file.add_action("Quit", shortcut="Mod+Q", on_click=shell.close)
25        bar.add_spacer()
26        bar.add_button(icon="search", on_click=lambda: None)
27        bar.add_button(icon="cart", on_click=lambda: None)
28        bar.add_theme_toggle()
29
30    category = bs.Signal("All")
31    max_price = bs.Signal(100)
32    results = bs.Signal("")
33
34    def recompute(*_):
35        matches = [
36            p for p in PRODUCTS
37            if (category() == "All" or p["category"] == category())
38            and p["price"] <= max_price()
39        ]
40        lines = [f"{p['name']} — ${p['price']}" for p in matches]
41        results.set("\n".join(lines) if lines else "No products match.")
42
43    # A bespoke filter sidebar — not navigation, so custom_nav() is the right tool.
44    with shell.custom_nav():
45        with bs.Column(horizontal_items="left", gap=12, padding=(16, 10)):
46            bs.Label("Filters", font="heading-md")
47            bs.Label("Category", font="caption")
48            bs.SelectButton(options=["All", "Electronics", "Home"], signal=category)
49            bs.Label("Max price", font="caption")
50            bs.Slider(min_value=10, max_value=100, signal=max_price)
51
52    # Drive the content region by hand from the filter signals.
53    with shell.content:
54        with bs.Column(horizontal_items="left", gap=8, padding=(16, 10)):
55            bs.Label("Results", font="heading-lg")
56            bs.Label(textsignal=results)
57
58    category.subscribe(recompute)
59    max_price.subscribe(recompute)
60    recompute()
61
62shell.run()

When to use#

Reach for a custom sidebar only when page_nav, list_nav, and tree_nav genuinely can’t express what you need — a sidebar that isn’t a navigation list. If you want collapsible sections of navigation, prefer a grouped sidebar (or an Accordion inside the panel for collapsible content). For records, use a list or tree master–detail.