Offcanvas
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 220
from shiny import App, Inputs, Outputs, Session, render, ui
app_ui = ui.page_fluid(
ui.offcanvas(
ui.p("An offcanvas panel slides in from the edge of the page."),
ui.p("Close it with the button, the backdrop, or the Escape key."),
title="Details",
trigger=ui.input_action_button("open", "Open panel"),
id="panel",
),
ui.output_code("state"),
)
def server(input: Inputs, output: Outputs, session: Session):
@render.code
def state():
return f"Panel is {'open' if input.panel() else 'closed'}"
app = App(app_ui, server=server)from shiny.express import input, render, ui
ui.offcanvas(
ui.p("An offcanvas panel slides in from the edge of the page."),
ui.p("Close it with the button, the backdrop, or the Escape key."),
title="Details",
trigger=ui.input_action_button("open", "Open panel"),
id="panel",
)
@render.code
def state():
return f"Panel is {'open' if input.panel() else 'closed'}" from shiny import App, Inputs, Outputs, Session, render, ui
app_ui = ui.page_fluid(
ui.offcanvas(
ui.p("An offcanvas panel slides in from the edge of the page."),
ui.p("Close it with the button, the backdrop, or the Escape key."),
title="Details",
trigger=ui.input_action_button("open", "Open panel"),
id="panel",
),
ui.output_code("state"),
)
def server(input: Inputs, output: Outputs, session: Session):
@render.code
def state():
return f"Panel is {'open' if input.panel() else 'closed'}"
app = App(app_ui, server=server)Relevant Functions
-
ui.offcanvas
ui.offcanvas(*args, title=None, footer=None, trigger=None, id=None, placement='right', width=None, height=None, close_button=True, backdrop=True, scroll=False, keyboard=True, **kwargs) -
ui.show_offcanvas
ui.show_offcanvas(offcanvas, *, session=None) -
ui.hide_offcanvas
ui.hide_offcanvas(id, *, session=None) -
ui.toggle_offcanvas
ui.toggle_offcanvas(id, show=None, *, session=None)
Details
An offcanvas panel slides in from an edge of the page, on top of the rest of the app, and stays there until it is dismissed. It is a good place for content that is useful but secondary — filters, help text, details about the selected row — that you don’t want taking up room in the main layout.
Create one with ui.offcanvas(), passing the panel’s body as positional arguments. title adds a header, and footer adds content pinned below the body. In both Shiny Core and Shiny Express, ui.offcanvas() is a regular function call rather than a context manager: put it anywhere in your UI, and it renders nothing visible until it is opened.
Every panel needs a way to be opened, so ui.offcanvas() requires either a trigger or an id:
triggertakes a UI element — usually an action button or a link — that opens the panel when clicked. The trigger is rendered where you placed theui.offcanvas()call, as a sibling of the panel itself.idlets the server open and close the panel withui.toggle_offcanvas()andui.hide_offcanvas(), and makes the panel’s open/closed state available as a reactive value,input.<id>().
You can use both together, as the example above does: a button opens the panel, and input.panel() reports whether it is open.
Dismissing a panel
By default a panel can be dismissed in three ways: the close button in its header, clicking the backdrop behind it, or pressing Esc. Each is adjustable:
close_button=Falseremoves the close button from the header.backdrop=Falserenders no backdrop at all, leaving the rest of the app clickable while the panel is open;backdrop='static'keeps the backdrop but ignores clicks on it, so the panel can’t be closed by accident.keyboard=Falsestops Esc from closing the panel.scroll=Truelets the page behind the panel keep scrolling while the panel is open.
If you turn off enough of these that the user has no way out, be sure the server closes the panel for them with ui.hide_offcanvas().
Building a panel from the server
ui.show_offcanvas() inserts a panel into the page and opens it in one step. Unlike the other two mutators it takes the object returned by ui.offcanvas(), not an id — ui.show_offcanvas("my_id") raises AttributeError. That makes it the right choice when the panel’s content depends on the state of the app; to open a panel that is already in your UI, use ui.toggle_offcanvas(id, show=True) instead. If you give that panel an id, it stays in the page after it is closed, and calling ui.show_offcanvas() again simply reveals it — the content is not re-rendered. To keep an id’d panel’s content current, put reactive outputs inside it and let them update; without an id, the panel is temporary and is removed from the page when it closes.
Accessibility
The panel is labelled by its title, so a panel without one is unlabelled for screen readers; ui.offcanvas() warns in that case. If you intentionally omit title, pass an aria-label (or aria-labelledby) attribute instead:
Prefer a natively keyboard-accessible trigger, such as a button or a link, so the panel can be opened without a mouse.
See Also: a sidebar holds secondary content permanently alongside your app rather than on top of it, and collapses on narrow screens. Modal messages also cover the page, but demand attention now rather than offering content on the side. Popovers and tooltips attach a small amount of content to a specific element.
Variations
Choose which edge the panel comes from
placement sets the edge the panel slides in from: 'right' (the default), 'left', 'top', or 'bottom'. Size a left or right panel with width, and a top or bottom panel with height. Setting width on a top or bottom panel (or height on a left or right one) has no effect and emits a warning.
#| '!! 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 shiny import App, ui
app_ui = ui.page_fluid(
ui.offcanvas(
ui.p("This panel slides in from the left."),
title="Left",
placement="left",
width=300,
trigger=ui.input_action_button("open_left", "From the left"),
id="left_panel",
),
ui.offcanvas(
ui.p("This panel slides in from the top."),
title="Top",
placement="top",
height=200,
trigger=ui.input_action_button("open_top", "From the top"),
id="top_panel",
),
ui.offcanvas(
ui.p("This panel slides in from the bottom."),
title="Bottom",
placement="bottom",
height=200,
trigger=ui.input_action_button("open_bottom", "From the bottom"),
id="bottom_panel",
),
)
def server(input, output, session):
pass
app = App(app_ui, server)from shiny.express import ui
ui.offcanvas(
ui.p("This panel slides in from the left."),
title="Left",
placement="left",
width=300,
trigger=ui.input_action_button("open_left", "From the left"),
id="left_panel",
)
ui.offcanvas(
ui.p("This panel slides in from the top."),
title="Top",
placement="top",
height=200,
trigger=ui.input_action_button("open_top", "From the top"),
id="top_panel",
)
ui.offcanvas(
ui.p("This panel slides in from the bottom."),
title="Bottom",
placement="bottom",
height=200,
trigger=ui.input_action_button("open_bottom", "From the bottom"),
id="bottom_panel",
)from shiny import App, ui
app_ui = ui.page_fluid(
ui.offcanvas(
ui.p("This panel slides in from the left."),
title="Left",
placement="left",
width=300,
trigger=ui.input_action_button("open_left", "From the left"),
id="left_panel",
),
ui.offcanvas(
ui.p("This panel slides in from the top."),
title="Top",
placement="top",
height=200,
trigger=ui.input_action_button("open_top", "From the top"),
id="top_panel",
),
ui.offcanvas(
ui.p("This panel slides in from the bottom."),
title="Bottom",
placement="bottom",
height=200,
trigger=ui.input_action_button("open_bottom", "From the bottom"),
id="bottom_panel",
),
)
def server(input, output, session):
pass
app = App(app_ui, server)Open and close the panel from the server
Give the panel an id and it no longer needs a trigger of its own: call ui.toggle_offcanvas() to flip it (or pass show=True/show=False to force a direction) and ui.hide_offcanvas() to close it. The same id is also a reactive value, input.<id>(), that reports whether the panel is open. Note that opening a panel by id is ui.toggle_offcanvas(id, show=True), not ui.show_offcanvas() — that one takes the object returned by ui.offcanvas(), and passing it an id string raises AttributeError. Note also where the Hide button lives: while the panel is open, its backdrop covers the rest of the page, so a control that closes the panel belongs inside the panel.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 300
from shiny import App, Inputs, Outputs, Session, reactive, render, ui
app_ui = ui.page_fluid(
ui.offcanvas(
ui.p("This panel has no trigger of its own; the server opens and closes it."),
ui.input_action_button("hide", "Hide"),
title="Server controlled",
id="panel",
),
ui.input_action_button("show", "Show"),
ui.input_action_button("toggle", "Toggle"),
ui.output_code("state"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.effect
@reactive.event(input.show)
def _():
ui.toggle_offcanvas("panel", show=True)
@reactive.effect
@reactive.event(input.hide)
def _():
ui.hide_offcanvas("panel")
@reactive.effect
@reactive.event(input.toggle)
def _():
ui.toggle_offcanvas("panel")
@render.code
def state():
return f"Panel is {'open' if input.panel() else 'closed'}"
app = App(app_ui, server=server)from shiny import reactive
from shiny.express import input, render, ui
ui.offcanvas(
ui.p("This panel has no trigger of its own; the server opens and closes it."),
ui.input_action_button("hide", "Hide"),
title="Server controlled",
id="panel",
)
ui.input_action_button("show", "Show")
ui.input_action_button("toggle", "Toggle")
@render.code
def state():
return f"Panel is {'open' if input.panel() else 'closed'}"
@reactive.effect
@reactive.event(input.show)
def show_panel():
ui.toggle_offcanvas("panel", show=True)
@reactive.effect
@reactive.event(input.hide)
def hide_panel():
ui.hide_offcanvas("panel")
@reactive.effect
@reactive.event(input.toggle)
def toggle_panel():
ui.toggle_offcanvas("panel") from shiny import App, Inputs, Outputs, Session, reactive, render, ui
app_ui = ui.page_fluid(
ui.offcanvas(
ui.p("This panel has no trigger of its own; the server opens and closes it."),
ui.input_action_button("hide", "Hide"),
title="Server controlled",
id="panel",
),
ui.input_action_button("show", "Show"),
ui.input_action_button("toggle", "Toggle"),
ui.output_code("state"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.effect
@reactive.event(input.show)
def _():
ui.toggle_offcanvas("panel", show=True)
@reactive.effect
@reactive.event(input.hide)
def _():
ui.hide_offcanvas("panel")
@reactive.effect
@reactive.event(input.toggle)
def _():
ui.toggle_offcanvas("panel")
@render.code
def state():
return f"Panel is {'open' if input.panel() else 'closed'}"
app = App(app_ui, server=server)Create a panel from the server
When the panel’s content isn’t known until the app is running, skip the UI entirely and build the panel in the server function, then insert and reveal it with ui.show_offcanvas(). It takes the object returned by ui.offcanvas() rather than an id.
#| '!! 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 shiny import App, Inputs, Outputs, Session, reactive, ui
app_ui = ui.page_fluid(
ui.input_action_button("show", "Show panel"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.effect
@reactive.event(input.show)
def _():
ui.show_offcanvas(
ui.offcanvas(
ui.p("This panel was created by the server, not the UI."),
title="Server panel",
placement="left",
id="server_panel",
)
)
app = App(app_ui, server=server)from shiny import reactive
from shiny.express import input, ui
ui.input_action_button("show", "Show panel")
@reactive.effect
@reactive.event(input.show)
def show_server_panel():
ui.show_offcanvas(
ui.offcanvas(
ui.p("This panel was created by the server, not the UI."),
title="Server panel",
placement="left",
id="server_panel",
)
)from shiny import App, Inputs, Outputs, Session, reactive, ui
app_ui = ui.page_fluid(
ui.input_action_button("show", "Show panel"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.effect
@reactive.event(input.show)
def _():
ui.show_offcanvas(
ui.offcanvas(
ui.p("This panel was created by the server, not the UI."),
title="Server panel",
placement="left",
id="server_panel",
)
)
app = App(app_ui, server=server)