Toolbar

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 200

from faicons import icon_svg
from shiny.express import input, render, ui

with ui.card(full_screen=True):
    with ui.card_header():
        "Header"
        with ui.toolbar(align="right"):
            ui.toolbar_input_button(
                id="action1",
                label="Refresh",
                icon=icon_svg("arrows-rotate"),
            )
            ui.toolbar_divider()
            ui.toolbar_input_select(
                id="options",
                label="Filter",
                choices=["ABC", "CDE", "EFG"],
            )

    @render.text
    def toolbar_status():
        return f"Button clicks: {input.action1()}, Selected: {input.options()}"
from faicons import icon_svg
from shiny.express import input, render, ui

with ui.card(full_screen=True):
    with ui.card_header():
        "Header"
        with ui.toolbar(align="right"):
            ui.toolbar_input_button(
                id="action1",
                label="Refresh",
                icon=icon_svg("arrows-rotate"),
            )
            ui.toolbar_divider()
            ui.toolbar_input_select(
                id="options",
                label="Filter",
                choices=["ABC", "CDE", "EFG"],
            )

    @render.text
    def toolbar_status():
        return f"Button clicks: {input.action1()}, Selected: {input.options()}"
from faicons import icon_svg
from shiny import App, render, ui

app_ui = ui.page_fixed(
    ui.card(
        ui.card_header(
            "Header",
            ui.toolbar(
                ui.toolbar_input_button(
                    id="action1",
                    label="Refresh",
                    icon=icon_svg("arrows-rotate"),
                ),
                ui.toolbar_divider(),
                ui.toolbar_input_select(
                    id="options",
                    label="Filter",
                    choices=["ABC", "CDE", "EFG"],
                ),
                align="right",
            ),
        ),
        ui.card_body(
            ui.output_text("toolbar_status"),
        ),
        full_screen=True,
    )
)

def server(input, output, session):
    @render.text
    def toolbar_status():
        return f"Button clicks: {input.action1()}, Selected: {input.options()}"

app = App(app_ui, server)
No matching items

Relevant Functions

  • ui.toolbar
    ui.toolbar(*args, align="right", gap=None, width=None)

  • ui.toolbar_input_button
    ui.toolbar_input_button(id, label, *, icon=None, show_label=MISSING, tooltip=MISSING, disabled=False, border=False, **kwargs)

  • ui.toolbar_input_select
    ui.toolbar_input_select(id, label, choices, *, selected=None, icon=None, show_label=False, tooltip=MISSING, **kwargs)

  • ui.toolbar_divider
    ui.toolbar_divider(width=None, gap=None)

  • ui.toolbar_spacer
    ui.toolbar_spacer()

No matching items

Details

A toolbar is a container for buttons, select inputs, and other UI elements in a compact form suitable for card headers, footers, and constrained spaces.

To make a toolbar:

  1. Create a toolbar with ui.toolbar(), typically within ui.card_header() or ui.card_footer(), or within an input component as seen in the examples below.

  2. Add toolbar elements inside, such as ui.toolbar_input_button() and ui.toolbar_input_select(). Use ui.toolbar_divider() to add visual separators and ui.toolbar_spacer() to create flexible space that pushes subsequent elements to the opposite end.

Toolbar alignment and width

By default, toolbars align to the right side of the container. Set align="left" to position the toolbar at the start of a card header or footer instead.

Set width="100%" on the toolbar to make it expand to fill available space. This is required for ui.toolbar_spacer() to push elements effectively and is automatically set when the toolbar is a direct child of a label element.

Toolbar elements

Toolbars can contain:

  • Buttons: Use ui.toolbar_input_button() for action buttons. When an icon is provided, the label is hidden by default but available to screen readers and tooltips.
  • Selects: Use ui.toolbar_input_select() for dropdown selections. Labels are visually hidden by default but available to screen readers.
  • Dividers: Use ui.toolbar_divider() to add visual separator lines between groups of elements.
  • Spacers: Use ui.toolbar_spacer() to create flexible space that pushes subsequent elements to the opposite end of the toolbar.

Toolbar with spacer

Use toolbar_spacer() to push elements to opposite ends of the toolbar.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 250

from faicons import icon_svg
from shiny.express import ui

with ui.card(full_screen=True):
    with ui.card_header():
        with ui.toolbar(align="left", width="100%"):
            ui.toolbar_input_button(
                id="left",
                label="Left",
                icon=icon_svg("arrow-left"),
            )
            ui.toolbar_input_button(
                id="right",
                label="Right",
                icon=icon_svg("arrow-right"),
            )
            ui.toolbar_input_button(
                id="refresh",
                label="Refresh",
                icon=icon_svg("arrows-rotate"),
            )
            ui.toolbar_spacer()
            ui.toolbar_input_button(
                id="export",
                label="Export",
                icon=icon_svg("download"),
            )
            ui.toolbar_input_button(
                id="save",
                label="Save",
                icon=icon_svg("floppy-disk"),
            )

    ui.p("Toolbar spacer pushes buttons to opposite ends.")
from faicons import icon_svg
from shiny.express import ui

with ui.card(full_screen=True):
    with ui.card_header():
        with ui.toolbar(align="left", width="100%"):
            ui.toolbar_input_button(
                id="left",
                label="Left",
                icon=icon_svg("arrow-left"),
            )
            ui.toolbar_input_button(
                id="right",
                label="Right",
                icon=icon_svg("arrow-right"),
            )
            ui.toolbar_input_button(
                id="refresh",
                label="Refresh",
                icon=icon_svg("arrows-rotate"),
            )
            ui.toolbar_spacer()
            ui.toolbar_input_button(
                id="export",
                label="Export",
                icon=icon_svg("download"),
            )
            ui.toolbar_input_button(
                id="save",
                label="Save",
                icon=icon_svg("floppy-disk"),
            )

    ui.p("Toolbar spacer pushes buttons to opposite ends.")
from faicons import icon_svg
from shiny import App, ui

app_ui = ui.page_fixed(
    ui.card(
        ui.card_header(
            ui.toolbar(
                ui.toolbar_input_button(
                    id="left",
                    label="Left",
                    icon=icon_svg("arrow-left"),
                ),
                ui.toolbar_input_button(
                    id="right",
                    label="Right",
                    icon=icon_svg("arrow-right"),
                ),
                ui.toolbar_input_button(
                    id="refresh",
                    label="Refresh",
                    icon=icon_svg("arrows-rotate"),
                ),
                ui.toolbar_spacer(),
                ui.toolbar_input_button(
                    id="export",
                    label="Export",
                    icon=icon_svg("download"),
                ),
                ui.toolbar_input_button(
                    id="save",
                    label="Save",
                    icon=icon_svg("floppy-disk"),
                ),
                align="left",
                width="100%",
            ),
        ),
        ui.card_body(
            ui.p("Toolbar spacer pushes buttons to opposite ends."),
        ),
        full_screen=True,
    )
)

def server(input, output, session):
    pass

app = App(app_ui, server)
No matching items

Toolbar in input components

You can use toolbars in the label of input components to provide additional information or action buttons integrated directly in the component.

Info tooltip on input label

Use toolbar_input_button() with a tooltip string to add an informational icon next to an input label. The button requires no server handler.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 350

from faicons import icon_svg
from shiny.express import ui

with ui.card():
    ui.card_header("Data Settings")
    ui.input_slider(
        "threshold",
        label=ui.toolbar(
            ui.toolbar_input_button(
                "threshold_info",
                label="About this setting",
                icon=icon_svg("circle-info"),
                tooltip="Standard deviations from the mean before a value is flagged as an outlier.",
            ),
            "Outlier threshold",
            align="left",
        ),
        min=1,
        max=5,
        value=2,
        step=0.5,
    )
    ui.input_numeric(
        "sample_size",
        label=ui.toolbar(
            ui.toolbar_input_button(
                "sample_info",
                label="About this setting",
                icon=icon_svg("circle-info"),
                tooltip="Number of observations to draw from the dataset for each analysis run.",
            ),
            "Sample size",
            align="left",
        ),
        value=100,
        min=10,
        max=1000,
        step=10,
    )
from faicons import icon_svg
from shiny.express import ui

with ui.card():
    ui.card_header("Data Settings")
    ui.input_slider(
        "threshold",
        label=ui.toolbar(
            ui.toolbar_input_button(
                "threshold_info",
                label="About this setting",
                icon=icon_svg("circle-info"),
                tooltip="Standard deviations from the mean before a value is flagged as an outlier.",
            ),
            "Outlier threshold",
            align="left",
        ),
        min=1,
        max=5,
        value=2,
        step=0.5,
    )
    ui.input_numeric(
        "sample_size",
        label=ui.toolbar(
            ui.toolbar_input_button(
                "sample_info",
                label="About this setting",
                icon=icon_svg("circle-info"),
                tooltip="Number of observations to draw from the dataset for each analysis run.",
            ),
            "Sample size",
            align="left",
        ),
        value=100,
        min=10,
        max=1000,
        step=10,
    )
from faicons import icon_svg
from shiny import App, ui

app_ui = ui.page_fluid(
    ui.card(
        ui.card_header("Data Settings"),
        ui.card_body(
            ui.input_slider(
                "threshold",
                label=ui.toolbar(
                    ui.toolbar_input_button(
                        "threshold_info",
                        label="About this setting",
                        icon=icon_svg("circle-info"),
                        tooltip="Standard deviations from the mean before a value is flagged as an outlier.",
                    ),
                    "Outlier threshold",
                    align="left",
                ),
                min=1,
                max=5,
                value=2,
                step=0.5,
            ),
            ui.input_numeric(
                "sample_size",
                label=ui.toolbar(
                    ui.toolbar_input_button(
                        "sample_info",
                        label="About this setting",
                        icon=icon_svg("circle-info"),
                        tooltip="Number of observations to draw from the dataset for each analysis run.",
                    ),
                    "Sample size",
                    align="left",
                ),
                value=100,
                min=10,
                max=1000,
                step=10,
            ),
        ),
    )
)

def server(input, output, session):
    pass

app = App(app_ui, server)
No matching items

Toolbar in input label

Use toolbar as the label for input_text_area() or input_numeric() to add formatting or action buttons directly in the input label.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 350

from faicons import icon_svg
from shiny.express import input, render, ui

with ui.card(full_screen=True):
    ui.card_header("Text Editor with Formatting Toolbar")

    with ui.div(class_="d-flex flex-column align-items-center", style="max-width: 600px; margin: 0 auto;"):
        ui.input_text_area(
            "content",
            label=ui.toolbar(
                ui.toolbar_input_button(
                    "bold",
                    label="Bold",
                    icon=icon_svg("bold"),
                ),
                ui.toolbar_input_button(
                    "italic",
                    label="Italic",
                    icon=icon_svg("italic"),
                ),
                ui.toolbar_input_button(
                    "code",
                    label="Code",
                    icon=icon_svg("code"),
                ),
                align="right",
            ),
            placeholder="Type your content here...",
            rows=8,
        )

        @render.text
        def formatting_status():
            return f"Bold: {input.bold()} | Italic: {input.italic()} | Code: {input.code()}"
from faicons import icon_svg
from shiny.express import input, render, ui

with ui.card(full_screen=True):
    ui.card_header("Text Editor with Formatting Toolbar")

    with ui.div(class_="d-flex flex-column align-items-center", style="max-width: 600px; margin: 0 auto;"):
        ui.input_text_area(
            "content",
            label=ui.toolbar(
                ui.toolbar_input_button(
                    "bold",
                    label="Bold",
                    icon=icon_svg("bold"),
                ),
                ui.toolbar_input_button(
                    "italic",
                    label="Italic",
                    icon=icon_svg("italic"),
                ),
                ui.toolbar_input_button(
                    "code",
                    label="Code",
                    icon=icon_svg("code"),
                ),
                align="right",
            ),
            placeholder="Type your content here...",
            rows=8,
        )

        @render.text
        def formatting_status():
            return f"Bold: {input.bold()} | Italic: {input.italic()} | Code: {input.code()}"
from faicons import icon_svg
from shiny import App, render, ui

app_ui = ui.page_fixed(
    ui.card(
        ui.card_header("Text Editor with Formatting Toolbar"),
        ui.card_body(
            ui.div(
                ui.input_text_area(
                    "content",
                    label=ui.toolbar(
                        ui.toolbar_input_button(
                            "bold",
                            label="Bold",
                            icon=icon_svg("bold"),
                        ),
                        ui.toolbar_input_button(
                            "italic",
                            label="Italic",
                            icon=icon_svg("italic"),
                        ),
                        ui.toolbar_input_button(
                            "code",
                            label="Code",
                            icon=icon_svg("code"),
                        ),
                        align="right",
                    ),
                    placeholder="Type your content here...",
                    rows=8,
                ),
                ui.output_text("formatting_status"),
                class_="d-flex flex-column align-items-center",
                style="max-width: 600px; margin: 0 auto;",
            ),
        ),
        full_screen=True,
    )
)

def server(input, output, session):
    @render.text
    def formatting_status():
        return f"Bold: {input.bold()} | Italic: {input.italic()} | Code: {input.code()}"

app = App(app_ui, server)
No matching items

Toolbar in submit textarea

Add a toolbar parameter to input_submit_textarea() to include toolbar inputs in the textarea’s submit area.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 600

from faicons import icon_svg
from shiny import reactive
from shiny.express import input, render, ui

ui.page_opts(fillable=False)

messages = reactive.value([])

with ui.card(full_screen=True, height="250px"):
    ui.card_header("Message Composer")
    with ui.card_body():
        ui.input_submit_textarea(
            "message",
            label="Message",
            placeholder="Compose your message...",
            rows=4,
            toolbar=ui.toolbar(
                ui.toolbar_input_select(
                    "priority",
                    label="Priority",
                    choices=["Low", "Medium", "High"],
                    selected="Medium",
                    icon=icon_svg("flag"),
                ),
                ui.toolbar_divider(),
                ui.toolbar_input_button(
                    "attach",
                    label="Attach",
                    icon=icon_svg("paperclip"),
                ),
                align="right",
            ),
        )

with ui.card(full_screen=True, height="250px"):
    ui.card_header("Sent Messages")

    with ui.card_body():
        @render.ui
        def messages_output():
            msg_list = messages.get()
            if not msg_list:
                return ui.p("No messages sent yet.", style="color: #888;")

            return ui.div(
                *[
                    ui.p(
                        f"[{msg['priority']}] {msg['text']}",
                        style="margin: 4px 0;",
                    )
                    for msg in reversed(msg_list)
                ]
            )

@reactive.effect
@reactive.event(input.message)
def _():
    message_text = input.message()
    if message_text and message_text.strip():
        current_messages = list(messages.get())
        current_messages.append(
            {"text": message_text, "priority": input.priority()}
        )
        messages.set(current_messages)
from faicons import icon_svg
from shiny import reactive
from shiny.express import input, render, ui

ui.page_opts(fillable=False)

messages = reactive.value([])

with ui.card(full_screen=True, height="250px"):
    ui.card_header("Message Composer")
    with ui.card_body():
        ui.input_submit_textarea(
            "message",
            label="Message",
            placeholder="Compose your message...",
            rows=4,
            toolbar=ui.toolbar(
                ui.toolbar_input_select(
                    "priority",
                    label="Priority",
                    choices=["Low", "Medium", "High"],
                    selected="Medium",
                    icon=icon_svg("flag"),
                ),
                ui.toolbar_divider(),
                ui.toolbar_input_button(
                    "attach",
                    label="Attach",
                    icon=icon_svg("paperclip"),
                ),
                align="right",
            ),
        )

with ui.card(full_screen=True, height="250px"):
    ui.card_header("Sent Messages")

    with ui.card_body():
        @render.ui
        def messages_output():
            msg_list = messages.get()
            if not msg_list:
                return ui.p("No messages sent yet.", style="color: #888;")

            return ui.div(
                *[
                    ui.p(
                        f"[{msg['priority']}] {msg['text']}",
                        style="margin: 4px 0;",
                    )
                    for msg in reversed(msg_list)
                ]
            )

@reactive.effect
@reactive.event(input.message)
def _():
    message_text = input.message()
    if message_text and message_text.strip():
        current_messages = list(messages.get())
        current_messages.append(
            {"text": message_text, "priority": input.priority()}
        )
        messages.set(current_messages)
from faicons import icon_svg
from shiny import App, reactive, render, ui

app_ui = ui.page_fixed(
    ui.card(
        ui.card_header("Message Composer"),
        ui.card_body(
            ui.input_submit_textarea(
                "message",
                label="Message",
                placeholder="Compose your message...",
                rows=4,
                toolbar=ui.toolbar(
                    ui.toolbar_input_select(
                        "priority",
                        label="Priority",
                        choices=["Low", "Medium", "High"],
                        selected="Medium",
                        icon=icon_svg("flag"),
                    ),
                    ui.toolbar_divider(),
                    ui.toolbar_input_button(
                        "attach",
                        label="Attach",
                        icon=icon_svg("paperclip"),
                    ),
                    align="right",
                ),
            ),
        ),
        full_screen=True,
        height="250px",
    ),
    ui.card(
        ui.card_header("Sent Messages"),
        ui.card_body(
            ui.output_ui("messages_output"),
        ),
        full_screen=True,
        height="250px",
    ),
)

def server(input, output, session):
    messages = reactive.value([])

    @render.ui
    def messages_output():
        msg_list = messages.get()
        if not msg_list:
            return ui.p("No messages sent yet.", style="color: #888;")

        return ui.div(
            *[
                ui.p(
                    f"[{msg['priority']}] {msg['text']}",
                    style="margin: 4px 0;",
                )
                for msg in reversed(msg_list)
            ]
        )

    @reactive.effect
    @reactive.event(input.message)
    def _():
        message_text = input.message()
        if message_text and message_text.strip():
            current_messages = list(messages.get())
            current_messages.append(
                {"text": message_text, "priority": input.priority()}
            )
            messages.set(current_messages)

app = App(app_ui, server)
No matching items

Accessibility

All toolbar inputs support full keyboard navigation and screen reader accessibility. Always provide meaningful labels for toolbar buttons and selects, even when using icon-only displays.

See Also: Toolbar Button | Toolbar Select | Card