Master–detail (list)#

A list of records in the sidebar drives a detail view in the content area — the email inbox, the contact list, the device manager. The sidebar is filled straight from a data source; you write one builder that renders the selected record.

Email inbox master–detail — light theme Email inbox master–detail — dark theme

How it works#

list_nav(source) fills the sidebar from a data source, rendering each record’s title / text / icon as a row. Decorate a builder with @shell.detail to render the body for the selected record — it receives the record as a dict. The first record is selected automatically, so the detail view is never empty on open.

shell.list_nav(inbox)

@shell.detail
def read(message):
    bs.Label(message["text"], font="heading-lg")
    bs.Label(message["body"])

The list stays in sync with its source — adding, removing, or editing records updates the sidebar (and re-renders the open detail) automatically.

Example#

 1"""Master–detail (list) — a list drives a detail view (an email inbox).
 2
 3``list_nav`` turns each record into a sidebar row (its ``title`` / ``text`` /
 4``icon``); ``@shell.detail`` renders the body for the selected record, received as
 5a dict. The first message is selected automatically — no ``navigate`` needed. The
 6inbox → read-the-message flow is the archetypal master–detail screen.
 7"""
 8import bootstack as bs
 9from bootstack.data import MemoryDataSource
10
11inbox = MemoryDataSource().load([
12    {"id": 1, "title": "Dana Reyes", "text": "Q3 roadmap review", "icon": "envelope",
13     "body": "Hi — can we move the roadmap review to Thursday? Thanks, Dana"},
14    {"id": 2, "title": "GitHub", "text": "[bootstack] PR #135 merged", "icon": "envelope-open",
15     "body": "Your pull request was merged into main."},
16    {"id": 3, "title": "Sam Okonkwo", "text": "Lunch tomorrow?", "icon": "envelope",
17     "body": "Want to grab lunch tomorrow around noon?"},
18    {"id": 4, "title": "Billing", "text": "Your receipt for June", "icon": "envelope-open",
19     "body": "Thanks for your payment. Your receipt is attached."},
20])
21
22with bs.AppShell(title="Mail", size=(900, 580)) as shell:
23    shell.commandbar.add_button(icon="pencil-square", label="Compose", on_click=lambda: None)
24    shell.commandbar.add_spacer()
25    shell.commandbar.add_button(icon="circle-half", on_click=bs.toggle_theme)
26
27    shell.list_nav(inbox, chevron=True)
28
29    @shell.detail
30    def read(message):
31        with bs.VStack(fill="both", expand=True, anchor_items="w", gap=12, padding=20):
32            bs.Label(message["text"], font="heading-lg")
33            bs.Label(f"From {message['title']}", font="caption")
34            bs.Separator(fill="x")
35            bs.Label(message["body"])
36
37shell.run()

When to use#

Use the list master–detail when the sidebar is a flat list of records and the content shows one at a time. When records nest under parents (folders, an org chart), use Master–detail (tree). When the sidebar items are a fixed set of authored screens rather than data, use a single-tier app.