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)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()
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:
Create a toolbar with
ui.toolbar(), typically withinui.card_header()orui.card_footer(), or within an input component as seen in the examples below.Add toolbar elements inside, such as
ui.toolbar_input_button()andui.toolbar_input_select(). Useui.toolbar_divider()to add visual separators andui.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)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)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)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)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