Cards and panels

Use cards and panels to define areas of related content in your Shiny app.

Use cards and panels to define areas of related content.

Relevant Functions

  • ui.card
    ui.card(*args, full_screen=False, height=None, max_height=None, min_height=None, fill=True, class_=None, id=None, **kwargs)

  • ui.card_header
    ui.card_header(*args, container=tags.div, **kwargs)

  • ui.card_body
    ui.card_body(*args, fillable=True, min_height=None, max_height=None, max_height_full_screen=MISSING, height=None, padding=None, gap=None, fill=True, class_=None, **kwargs)

  • ui.card_footer
    ui.card_footer(*args, **kwargs)

  • ui.panel_absolute
    ui.panel_absolute(*args, top=None, left=None, right=None, bottom=None, width=None, height=None, draggable=False, fixed=False, cursor='auto', **kwargs)

  • ui.panel_fixed
    ui.panel_fixed(*args, top=None, left=None, right=None, bottom=None, width=None, height=None, draggable=False, cursor='auto', **kwargs)

  • ui.panel_title
    ui.panel_title(title, window_title=MISSING)

  • ui.panel_conditional
    ui.panel_conditional(condition, *args, **kwargs)

No matching items

Cards

Cards are a common organizing unit for modern user interfaces (UI). At their core, they’re just rectangular containers with borders and padding. However, when utilized properly to group related information, they help users better digest, engage, and navigate through content.

Hello card()

A ui.card() is designed to handle any number of “known” card items (e.g., ui.card_header(), ui.card_body(), etc) as unnamed arguments (i.e., children). As we’ll see shortly, ui.card() also has some useful named arguments (e.g., full_screen, height, etc).

At their core, ui.card() and card items are just an HTML div() with a special Bootstrap class, so you can use Bootstrap’s utility classes to customize things like colors, text, borders, etc.

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

from shiny import App, ui

app_ui = ui.page_fluid(
    ui.card(
        ui.card_header(
            "A header",
            class_="bg-dark"
        ),
        ui.card_body(
            ui.markdown("Some text with a [link](https://github.com)")
        )
    )
)

def server(input, output, session):
    pass

app = App(app_ui, server)
from shiny.express import ui

with ui.card():
    ui.card_header("A header", class_="bg-dark")
    ui.markdown("Some text with a [link](https://github.com)")
from shiny import App, ui

app_ui = ui.page_fluid(
    ui.card(
        ui.card_header(
            "A header",
            class_="bg-dark"
        ),
        ui.card_body(
            ui.markdown("Some text with a [link](https://github.com)")
        )
    )
)

def server(input, output, session):
    pass

app = App(app_ui, server)
No matching items

Implicit card_body()

If you find yourself using ui.card_body() without changing any of its defaults, consider dropping it altogether since any direct children of ui.card() that aren’t “known” ui.card() items are wrapped together into an implicit ui.card_body() call.

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

from shiny import App, ui

app_ui = ui.page_fluid(
    ui.card(
        ui.card_header(
            "A header",
            class_="bg-dark"
        ),
        ui.markdown("Some text with a [link](https://github.com).")
    )
)

def server(input, output, session):
    pass

app = App(app_ui, server)
from shiny.express import ui

with ui.card():
    ui.card_header("A header", class_="bg-dark")
    ui.markdown("Some text with a [link](https://github.com).")
from shiny import App, ui

app_ui = ui.page_fluid(
    ui.card(
        ui.card_header(
            "A header",
            class_="bg-dark"
        ),
        ui.markdown("Some text with a [link](https://github.com).")
    )
)

def server(input, output, session):
    pass

app = App(app_ui, server)
No matching items

Restricting growth

By default, a ui.card()’s size grows to accommodate the size of its contents. Thus, if a ui.card_body() contains a large amount of text, tables, etc., you may want to specify a height or max_height. That said, when laying out multiple cards, it’s likely best not to specify height on the ui.card(), and instead, let the layout determine the height.

Although scrolling is convenient for reducing the amount of space required to park lots of content, it can also be a nuisance to the user. To help reduce the need for scrolling, consider pairing scrolling with full_screen=True (which adds an icon to expand the card’s size to the browser window). Notice how, when the card is expanded to full-screen, max_height/height won’t affect the full-screen size of the card.

#| '!! 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, ui

app_ui = ui.page_fluid(
    ui.card(
        ui.card_header("A long, scrolling, description"),
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. " * 15,
        max_height="250px",
        full_screen=True
    )
)

def server(input, output, session):
    pass

app = App(app_ui, server)
from shiny.express import ui

with ui.card(max_height="250px", full_screen=True):
    ui.card_header("A long, scrolling, description")
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " * 50
from shiny import App, ui

app_ui = ui.page_fluid(
    ui.card(
        ui.card_header("A long, scrolling, description"),
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " * 50,
        max_height="250px",
        full_screen=True
    )
)

def server(input, output, session):
    pass

app = App(app_ui, server)
No matching items

Filling outputs

A ui.card()’s default behavior is optimized for facilitating filling layouts. More specifically, if a fill item (e.g., ui.output_plot()), appears as a direct child of a ui.card_body(), it resizes to fit the ui.card()’s specified height. This means, by specifying height="250px" we’ve effectively shrunk the plot’s height from its default of 400 down to about 200 pixels. And, when expanded to full_screen, the plot grows to match the ui.card()’s new size.

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

from shiny import App, render, ui
import matplotlib.pyplot as plt

app_ui = ui.page_fluid(
    ui.card(
        ui.card_header("A filling plot"),
        ui.card_body(ui.output_plot("plot")),
        height="250px",
        full_screen=True
    )
)

def server(input, output, session):
    @render.plot
    def plot():
        fig, ax = plt.subplots()
        ax.plot([1, 2, 3], [1, 4, 9])
        return fig

app = App(app_ui, server)
from shiny.express import render, ui
import matplotlib.pyplot as plt

with ui.card(height="250px", full_screen=True):
    ui.card_header("A filling plot")

    @render.plot
    def plot():
        fig, ax = plt.subplots()
        ax.plot([1, 2, 3], [1, 4, 9])
        return fig
from shiny import App, render, ui
import matplotlib.pyplot as plt

app_ui = ui.page_fluid(
    ui.card(
        ui.card_header("A filling plot"),
        ui.card_body(ui.output_plot("plot")),
        height="250px",
        full_screen=True
    )
)

def server(input, output, session):
    @render.plot
    def plot():
        fig, ax = plt.subplots()
        ax.plot([1, 2, 3], [1, 4, 9])
        return fig

app = App(app_ui, server)
No matching items

Multiple cards

ui.layout_columns() is especially nice for laying out multiple cards since each card in a particular row will have the same height (by default).

#| '!! 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 shiny import App, render, ui

app_ui = ui.page_fluid(
    ui.layout_columns(
        ui.card(
            ui.card_header("User Settings"),
            ui.card_body(
                ui.input_text("name", "Name", "John Doe"),
                ui.input_slider("age", "Age", 18, 100, 30),
            ),
            ui.card_footer(
                ui.input_action_button("save", "Save Changes", class_="btn-primary")
            ),
            full_screen=True,
        ),
        ui.card(
            ui.card_header("Activity Summary"),
            ui.card_body(
                ui.output_text("summary"),
                ui.tags.ul(
                    ui.tags.li("Last login: 2 hours ago"),
                    ui.tags.li("Messages: 12 unread"),
                    ui.tags.li("Tasks: 5 pending"),
                ),
            ),
            full_screen=True,
        ),
        col_widths=[6, 6],
        height="300px",
    )
)

def server(input, output, session):
    @render.text
    def summary():
        return f"Hello, {input.name()}!"

app = App(app_ui, server)
from shiny.express import input, render, ui

with ui.layout_columns(col_widths=[6, 6], height="300px"):
    with ui.card(full_screen=True):
        ui.card_header("User Settings")
        with ui.card_body():
            ui.input_text("name", "Name", "John Doe")
            ui.input_slider("age", "Age", 18, 100, 30)
        with ui.card_footer():
            ui.input_action_button("save", "Save Changes", class_="btn-primary")

    with ui.card(full_screen=True):
        ui.card_header("Activity Summary")
        with ui.card_body():
            @render.text
            def summary():
                return f"Hello, {input.name()}!"

            ui.tags.ul(
                ui.tags.li("Last login: 2 hours ago"),
                ui.tags.li("Messages: 12 unread"),
                ui.tags.li("Tasks: 5 pending"),
            )
from shiny import App, render, ui

app_ui = ui.page_fluid(
    ui.layout_columns(
        ui.card(
            ui.card_header("User Settings"),
            ui.card_body(
                ui.input_text("name", "Name", "John Doe"),
                ui.input_slider("age", "Age", 18, 100, 30),
            ),
            ui.card_footer(
                ui.input_action_button("save", "Save Changes", class_="btn-primary")
            ),
            full_screen=True,
        ),
        ui.card(
            ui.card_header("Activity Summary"),
            ui.card_body(
                ui.output_text("summary"),
                ui.tags.ul(
                    ui.tags.li("Last login: 2 hours ago"),
                    ui.tags.li("Messages: 12 unread"),
                    ui.tags.li("Tasks: 5 pending"),
                ),
            ),
            full_screen=True,
        ),
        col_widths=[6, 6],
        height="300px",
    )
)

def server(input, output, session):
    @render.text
    def summary():
        return f"Hello, {input.name()}!"

app = App(app_ui, server)
No matching items

Follow these steps to create an app with content separated into cards:

  1. Use ui.layout_columns() to arrange multiple cards in a grid. Set col_widths to control column widths using Bootstrap’s 12-column grid (e.g., col_widths=[6, 6] for two equal-width cards per row).

  2. Add ui.card() calls inside ui.layout_columns(). You can use ui.card_header() and ui.card_footer() to create card headers and footers.

  3. Control the appearance and functionality of each card by passing additional arguments to ui.card(). For example, set full_screen=True to allow users to expand the card to fullscreen.

Tabbed card

Use ui.navset_card_underline() inside a card to create tabbed content.

#| '!! 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.navset_card_underline(
        ui.nav_spacer(),
        ui.nav_panel(
            "Overview",
            "This project has 3 milestones and 12 tasks.",
            ui.br(),
            "Current status: On track",
        ),
        ui.nav_panel(
            "Team",
            ui.tags.ul(
                ui.tags.li("Alice (Lead)"),
                ui.tags.li("Bob (Developer)"),
                ui.tags.li("Carol (Designer)"),
            ),
        ),
        ui.nav_panel(
            "Timeline",
            "Start: January 2026",
            ui.br(),
            "Expected completion: June 2026",
        ),
        title="Project Dashboard",
    ),
    full_screen=True,
    height="300px",
)

def server(input, output, session):
    pass

app = App(app_ui, server)
from shiny.express import ui

ui.page_opts(full_screen=True, height="300px")

with ui.navset_card_underline(title="Project Dashboard"):
    ui.nav_spacer()
    with ui.nav_panel("Overview"):
        "This project has 3 milestones and 12 tasks."
        ui.br()
        "Current status: On track"
    with ui.nav_panel("Team"):
        ui.tags.ul(
            ui.tags.li("Alice (Lead)"),
            ui.tags.li("Bob (Developer)"),
            ui.tags.li("Carol (Designer)"),
        )
    with ui.nav_panel("Timeline"):
        "Start: January 2026"
        ui.br()
        "Expected completion: June 2026"
from shiny import App, ui

app_ui = ui.page_fluid(
    ui.navset_card_underline(
        ui.nav_spacer(),
        ui.nav_panel(
            "Overview",
            "This project has 3 milestones and 12 tasks.",
            ui.br(),
            "Current status: On track",
        ),
        ui.nav_panel(
            "Team",
            ui.tags.ul(
                ui.tags.li("Alice (Lead)"),
                ui.tags.li("Bob (Developer)"),
                ui.tags.li("Carol (Designer)"),
            ),
        ),
        ui.nav_panel(
            "Timeline",
            "Start: January 2026",
            ui.br(),
            "Expected completion: June 2026",
        ),
        title="Project Dashboard",
    ),
    full_screen=True,
    height="300px",
)

def server(input, output, session):
    pass

app = App(app_ui, server)
No matching items

Card with sidebar

Use ui.layout_sidebar() inside a card to create a card with a sidebar layout. This pattern is useful for cards that need input controls or filters alongside their main content.

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

import pandas as pd
from shiny import App, reactive, render, ui

# Create a small flower dataset
flowers = pd.DataFrame({
    "flower": ["Rose", "Tulip", "Daisy", "Orchid", "Lily", "Sunflower", "Violet", "Poppy"],
    "species": ["Rosa", "Tulipa", "Bellis", "Orchidaceae", "Lilium", "Helianthus", "Viola", "Papaver"],
    "color": ["Red", "Yellow", "White", "Purple", "Pink", "Yellow", "Purple", "Red"],
    "petal_length": [5.2, 3.8, 2.1, 6.3, 4.5, 8.1, 2.8, 3.5],
})

app_ui = ui.page_fluid(
    ui.card(
        ui.card_header("Flower Data Explorer"),
        ui.layout_sidebar(
            ui.sidebar(
                ui.input_select(
                    "color_filter",
                    "Filter by color",
                    ["All", "Red", "Yellow", "White", "Purple", "Pink"],
                ),
                ui.input_slider("rows", "Rows to show", 1, 8, 5),
                ui.input_action_button("reset", "Reset"),
            ),
            ui.card_body(
                ui.output_data_frame("flower_table"),
            ),
        ),
        full_screen=True,
        height="350px",
    )
)

def server(input, output, session):
    @render.data_frame
    def flower_table():
        # Filter by color
        if input.color_filter() == "All":
            df = flowers
        else:
            df = flowers[flowers["color"] == input.color_filter()]

        # Limit rows
        return df.head(input.rows())

    @reactive.effect
    @reactive.event(input.reset)
    def _():
        ui.update_select("color_filter", selected="All")
        ui.update_slider("rows", value=5)

app = App(app_ui, server)
import pandas as pd
from shiny.express import input, render, ui
from shiny import reactive

# Create a small flower dataset
flowers = pd.DataFrame({
    "flower": ["Rose", "Tulip", "Daisy", "Orchid", "Lily", "Sunflower", "Violet", "Poppy"],
    "species": ["Rosa", "Tulipa", "Bellis", "Orchidaceae", "Lilium", "Helianthus", "Viola", "Papaver"],
    "color": ["Red", "Yellow", "White", "Purple", "Pink", "Yellow", "Purple", "Red"],
    "petal_length": [5.2, 3.8, 2.1, 6.3, 4.5, 8.1, 2.8, 3.5],
})

with ui.card(full_screen=True, height="350px"):
    ui.card_header("Flower Data Explorer")
    with ui.layout_sidebar():
        with ui.sidebar():
            ui.input_select(
                "color_filter",
                "Filter by color",
                ["All", "Red", "Yellow", "White", "Purple", "Pink"],
            )
            ui.input_slider("rows", "Rows to show", 1, 8, 5)
            ui.input_action_button("reset", "Reset")
        with ui.card_body():

            @render.data_frame
            def flower_table():
                # Filter by color
                if input.color_filter() == "All":
                    df = flowers
                else:
                    df = flowers[flowers["color"] == input.color_filter()]

                # Limit rows
                return df.head(input.rows())

        @reactive.effect
        @reactive.event(input.reset)
        def _():
            ui.update_select("color_filter", selected="All")
            ui.update_slider("rows", value=5)
import pandas as pd
from shiny import App, reactive, render, ui

# Create a small flower dataset
flowers = pd.DataFrame({
    "flower": ["Rose", "Tulip", "Daisy", "Orchid", "Lily", "Sunflower", "Violet", "Poppy"],
    "species": ["Rosa", "Tulipa", "Bellis", "Orchidaceae", "Lilium", "Helianthus", "Viola", "Papaver"],
    "color": ["Red", "Yellow", "White", "Purple", "Pink", "Yellow", "Purple", "Red"],
    "petal_length": [5.2, 3.8, 2.1, 6.3, 4.5, 8.1, 2.8, 3.5],
})

app_ui = ui.page_fluid(
    ui.card(
        ui.card_header("Flower Data Explorer"),
        ui.layout_sidebar(
            ui.sidebar(
                ui.input_select(
                    "color_filter",
                    "Filter by color",
                    ["All", "Red", "Yellow", "White", "Purple", "Pink"],
                ),
                ui.input_slider("rows", "Rows to show", 1, 8, 5),
                ui.input_action_button("reset", "Reset"),
            ),
            ui.card_body(
                ui.output_data_frame("flower_table"),
            ),
        ),
        full_screen=True,
        height="350px",
    )
)

def server(input, output, session):
    @render.data_frame
    def flower_table():
        # Filter by color
        if input.color_filter() == "All":
            df = flowers
        else:
            df = flowers[flowers["color"] == input.color_filter()]

        # Limit rows
        return df.head(input.rows())

    @reactive.effect
    @reactive.event(input.reset)
    def _():
        ui.update_select("color_filter", selected="All")
        ui.update_slider("rows", value=5)

app = App(app_ui, server)
No matching items

Floating panel

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

## file: app.py
from htmltools import css
from shiny import App, ui

app_ui = ui.page_fillable(
    ui.panel_absolute(  
        ui.card(
            ui.card_header("Draggable panel"),
            "Move this panel anywhere you want.",
        ),
        width="300px",  
        right="50px",  
        top="50px",  
        draggable=True,  
    ),  
    style=css(
        background_image="url(https://unsplash.com/photos/XKXGghL7GQc/download?force=true&w=1920)",
        background_repeat="no-repeat",
        background_size="cover",
        background_position="center bottom",
    ),
)


def server(input, output, session):
    pass


app = App(app_ui, server)
from functools import partial

from htmltools import css
from shiny.express import ui
from shiny.ui import page_fillable

page_style = css(
    background_image="url(https://unsplash.com/photos/XKXGghL7GQc/download?force=true&w=1920)",
    background_repeat="no-repeat",
    background_size="cover",
    background_position="center bottom",
)

ui.page_opts(page_fn=partial(page_fillable, style=page_style))

with ui.panel_absolute(  
    width="300px",  
    right="50px",  
    top="50px",  
    draggable=True,  
):  
    with ui.card():
        ui.card_header("Draggable panel")
        "Move this panel anywhere you want."
from htmltools import css
from shiny import App, ui

app_ui = ui.page_fillable(
    ui.panel_absolute(  
        ui.card(
            ui.card_header("Draggable panel"),
            "Move this panel anywhere you want.",
        ),
        width="300px",  
        right="50px",  
        top="50px",  
        draggable=True,  
    ),  
    style=css(
        background_image="url(https://unsplash.com/photos/XKXGghL7GQc/download?force=true&w=1920)",
        background_repeat="no-repeat",
        background_size="cover",
        background_position="center bottom",
    ),
)


def server(input, output, session):
    pass


app = App(app_ui, server)

Follow these steps to create an app that has a panel floating over a main image.

First, to create the floating panel:

  1. Pass ui.panel_absolute() as the second argument of your Shiny UI page method, after ui.img(). Pass elements that you want to appear inside the panel to ui.panel_absolute().

  2. Position the panel using the top, bottom, left, and/or right parameters. Set the size of the panel using the height and/or width parameters.

  3. If you want the panel to be draggable, set the draggable parameter to True.

In the example above, we used CSS to add a scaling background image to the page. You can also use ui.img() to create this effect:

  1. Pass ui.img() to any Shiny UI page method (e.g., ui.page_fluid()). ui.img() creates an image.

  2. Pass the path or URL of your desired image to ui.img()’s src parameter. Set additional parameters to control the appearance of the image (e.g., width and height).

See also: ui.panel_fixed(). ui.panel_fixed() is equivalent to calling ui.panel_absolute() with fixed=True (i.e., the panel does not scroll with the rest of the page).

Conditional panel

Use ui.panel_conditional() to show or hide a group of UI elements based on the state of your inputs, without a round trip to the server.

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

## file: app.py
from shiny import App, ui

app_ui = ui.page_fluid(
    ui.input_checkbox("show", "Show advanced options", False),
    ui.panel_conditional(  
        "input.show",  
        ui.input_radio_buttons("kind", "Control kind", ["slider", "select"]),
    ),  
    ui.panel_conditional(  
        "input.show && input.kind === 'slider'",  
        ui.input_slider("slider", None, min=0, max=100, value=50),
    ),  
    ui.panel_conditional(  
        "input.show && input.kind === 'select'",  
        ui.input_select("select", None, ["A", "B", "C"]),
    ),  
)


def server(input, output, session):
    pass


app = App(app_ui, server)
from shiny.express import ui

ui.input_checkbox("show", "Show advanced options", False)

with ui.panel_conditional("input.show"):  
    ui.input_radio_buttons("kind", "Control kind", ["slider", "select"])

with ui.panel_conditional("input.show && input.kind === 'slider'"):  
    ui.input_slider("slider", None, min=0, max=100, value=50)

with ui.panel_conditional("input.show && input.kind === 'select'"):  
    ui.input_select("select", None, ["A", "B", "C"])
from shiny import App, ui

app_ui = ui.page_fluid(
    ui.input_checkbox("show", "Show advanced options", False),
    ui.panel_conditional(  
        "input.show",  
        ui.input_radio_buttons("kind", "Control kind", ["slider", "select"]),
    ),  
    ui.panel_conditional(  
        "input.show && input.kind === 'slider'",  
        ui.input_slider("slider", None, min=0, max=100, value=50),
    ),  
    ui.panel_conditional(  
        "input.show && input.kind === 'select'",  
        ui.input_select("select", None, ["A", "B", "C"]),
    ),  
)


def server(input, output, session):
    pass


app = App(app_ui, server)

Follow these steps to add a conditional panel to your app:

  1. Add ui.panel_conditional() inside any Shiny UI page method (e.g., ui.page_fluid()).

  2. Pass a condition as the first argument. This is a JavaScript expression written as a string, evaluated in the browser. The panel’s contents are shown whenever the expression is truthy.

  3. Pass the UI elements you want to show or hide as the remaining arguments.

Inside the condition, refer to inputs as input.<id> — for example "input.show". Conditions can combine inputs with JavaScript operators, as in "input.show && input.kind === 'slider'".

ImportantThe condition is JavaScript, not Python

This is the most common source of surprises with ui.panel_conditional(). The string is evaluated by the browser, so it follows JavaScript rules, not Python ones:

  • Use &&, ||, and ! — not and, or, and not.
  • Compare with === / !==, and quote string values: "input.kind === 'slider'".
  • JavaScript booleans are true and false, lowercase.
  • Access inputs as input.my_id — dot notation, and no trailing ().

A mistake in the expression is not a Python error, so your app still starts normally and the panel simply never appears. The browser console reports what went wrong — writing "input.show and true" logs Error parsing expression: input.show and true. Check there first when a conditional panel refuses to show.

Because the condition never reaches Python, showing and hiding is instant and does not invalidate any reactive outputs. But it also means the condition cannot use reactive values or any other server-side state. When your visibility logic depends on something only the server knows, render the UI dynamically with @render.ui instead.

TipInside a module, use the plain input id

ui.panel_conditional() records the module’s namespace alongside the condition, and Shiny narrows the input object to that module before evaluating it. So within a module you write the same "input.show" you would write in a standalone app — not the namespaced "input['my_module-show']", which will not match anything.

Note

Hidden content is still sent to the browser and still exists in the page — ui.panel_conditional() only controls visibility. Do not use it to hide sensitive values.