` and ``) have automatic height. They determine their own height based on the height of their contained elements. However, `ui.page_fillable(ui.output_plot("plot", height="100%"))` will work as expected because `ui.page_fillable()` fixes the `` height at 100% of the window height.
-
-Note that `ui.page_fillable(ui.output_plot("plot"))` may not cause the plot to fill the page. Like most Shiny output widgets, `ui.output_plot()`'s default height is a fixed number of pixels. You must explicitly set `height="100%"` if you want a plot (or htmlwidget, say) to fill its container.
-
-One must be careful what layouts/panels/elements come between the `ui.page_fillable()` and the plots/widgets. Any container that has an automatic height will cause children with `height="100%"` to misbehave. Stick to functions that are designed for fill layouts, such as the ones in the Shiny package.
-
-
-
diff --git a/layouts/arrange/app-column-nest-core.py b/layouts/arrange/app-column-nest-core.py
new file mode 100644
index 00000000..e59f45b0
--- /dev/null
+++ b/layouts/arrange/app-column-nest-core.py
@@ -0,0 +1,23 @@
+from shiny import App, ui
+
+app_ui = ui.page_fillable(
+ ui.layout_columns(
+ ui.card("Card 1"),
+ ui.card(
+ "Card 2",
+ ui.layout_columns(
+ ui.card("Card 2.1"),
+ ui.card("Card 2.2"),
+ width=1 / 2,
+ ),
+ ),
+ col_widths=(4, 8),
+ ),
+)
+
+
+def server(input, output, session):
+ return None
+
+
+app = App(app_ui, server)
diff --git a/layouts/arrange/app-column-nest-express.py b/layouts/arrange/app-column-nest-express.py
new file mode 100644
index 00000000..826da0fc
--- /dev/null
+++ b/layouts/arrange/app-column-nest-express.py
@@ -0,0 +1,15 @@
+from shiny.express import ui
+
+ui.page_opts(fillable=True)
+
+with ui.layout_columns(col_widths=(4, 8)):
+ with ui.card():
+ "Card 1"
+ with ui.card():
+ "Card 2"
+
+ with ui.layout_column_wrap(width=1 / 2):
+ with ui.card():
+ "Card 2.1"
+ with ui.card():
+ "Card 2.2"
diff --git a/layouts/arrange/app-fillable-core.py b/layouts/arrange/app-fillable-core.py
new file mode 100644
index 00000000..5f4df2c1
--- /dev/null
+++ b/layouts/arrange/app-fillable-core.py
@@ -0,0 +1,26 @@
+import matplotlib.pyplot as plt
+import numpy as np
+from shiny import App, render, ui
+
+app_ui = ui.page_fillable( # <<
+ ui.layout_sidebar(
+ ui.panel_sidebar(
+ ui.input_slider("n", "N", 0, 100, 20),
+ ),
+ ui.panel_main(
+ ui.output_plot("histogram", height="100%"), # <<
+ ),
+ ),
+) # <<
+
+
+def server(input, output, session):
+ @output
+ @render.plot(alt="A histogram")
+ def histogram():
+ np.random.seed(19680801)
+ x = 100 + 15 * np.random.randn(437)
+ plt.hist(x, input.n(), density=True)
+
+
+app = App(app_ui, server, debug=True)
diff --git a/layouts/arrange/app-fillable-express.py b/layouts/arrange/app-fillable-express.py
new file mode 100644
index 00000000..14ac57f8
--- /dev/null
+++ b/layouts/arrange/app-fillable-express.py
@@ -0,0 +1,15 @@
+import matplotlib.pyplot as plt
+import numpy as np
+from shiny.express import input, render, ui
+
+ui.page_opts(fillable=True)
+
+with ui.sidebar():
+ ui.input_slider("n", "N", 0, 100, 20)
+
+
+@render.plot(alt="A histogram")
+def histogram():
+ np.random.seed(19680801)
+ x = 100 + 15 * np.random.randn(437)
+ plt.hist(x, input.n(), density=True)
diff --git a/layouts/arrange/app-layout-column-wrap-core.py b/layouts/arrange/app-layout-column-wrap-core.py
new file mode 100644
index 00000000..57785f31
--- /dev/null
+++ b/layouts/arrange/app-layout-column-wrap-core.py
@@ -0,0 +1,17 @@
+from shiny import App, ui
+
+app_ui = ui.page_fillable(
+ "ui.layout_column_wrap()",
+ ui.layout_column_wrap(
+ ui.card("Card 1"),
+ ui.card("Card 2"),
+ ui.card("Card 3"),
+ ),
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server=server)
diff --git a/layouts/arrange/app-layout-column-wrap-dynamic-core.py b/layouts/arrange/app-layout-column-wrap-dynamic-core.py
new file mode 100644
index 00000000..6d0bc59b
--- /dev/null
+++ b/layouts/arrange/app-layout-column-wrap-dynamic-core.py
@@ -0,0 +1,18 @@
+from shiny import App, ui
+
+app_ui = ui.page_fillable(
+ ui.layout_column_wrap( # <<
+ ui.card("Card 1"),
+ ui.card("Card 2"),
+ ui.card("Card 3"),
+ ui.card("Card 4"),
+ width="300px", # <<
+ ),
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server=server)
diff --git a/layouts/arrange/app-layout-column-wrap-dynamic-express.py b/layouts/arrange/app-layout-column-wrap-dynamic-express.py
new file mode 100644
index 00000000..357f18ca
--- /dev/null
+++ b/layouts/arrange/app-layout-column-wrap-dynamic-express.py
@@ -0,0 +1,13 @@
+from shiny.express import ui
+
+ui.page_opts(fillable=True)
+
+with ui.layout_column_wrap(width="300px"): # <<
+ with ui.card():
+ "Card 1"
+ with ui.card():
+ "Card 2"
+ with ui.card():
+ "Card 3"
+ with ui.card():
+ "Card 4"
diff --git a/layouts/arrange/app-layout-column-wrap-express.py b/layouts/arrange/app-layout-column-wrap-express.py
new file mode 100644
index 00000000..9a8fe760
--- /dev/null
+++ b/layouts/arrange/app-layout-column-wrap-express.py
@@ -0,0 +1,13 @@
+from shiny.express import ui
+
+ui.page_opts(fillable=True)
+
+"ui.layout_column_wrap()"
+
+with ui.layout_column_wrap():
+ with ui.card():
+ "Card 1"
+ with ui.card():
+ "Card 2"
+ with ui.card():
+ "Card 3"
diff --git a/layouts/arrange/app-layout-column-wrap-half-core.py b/layouts/arrange/app-layout-column-wrap-half-core.py
new file mode 100644
index 00000000..8e193f77
--- /dev/null
+++ b/layouts/arrange/app-layout-column-wrap-half-core.py
@@ -0,0 +1,18 @@
+from shiny import App, ui
+
+app_ui = ui.page_fillable(
+ ui.layout_column_wrap( # <<
+ ui.card("Card 1"),
+ ui.card("Card 2"),
+ ui.card("Card 3"),
+ ui.card("Card 4"),
+ width=1 / 2, # <<
+ ),
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server=server)
diff --git a/layouts/arrange/app-layout-column-wrap-half-express.py b/layouts/arrange/app-layout-column-wrap-half-express.py
new file mode 100644
index 00000000..29d5dbd5
--- /dev/null
+++ b/layouts/arrange/app-layout-column-wrap-half-express.py
@@ -0,0 +1,13 @@
+from shiny.express import ui
+
+ui.page_opts(fillable=True)
+
+with ui.layout_column_wrap(width=1 / 2): # <<
+ with ui.card():
+ "Card 1"
+ with ui.card():
+ "Card 2"
+ with ui.card():
+ "Card 3"
+ with ui.card():
+ "Card 4"
diff --git a/layouts/arrange/app-layout-columns-col-widths-core.py b/layouts/arrange/app-layout-columns-col-widths-core.py
new file mode 100644
index 00000000..3b00a175
--- /dev/null
+++ b/layouts/arrange/app-layout-columns-col-widths-core.py
@@ -0,0 +1,17 @@
+from shiny import App, ui
+
+app_ui = ui.page_fillable(
+ ui.layout_columns(
+ ui.card("Card 1"),
+ ui.card("Card 2"),
+ ui.card("Card 3"),
+ col_widths=(2, 4, 6),
+ ),
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server=server)
diff --git a/layouts/arrange/app-layout-columns-col-widths-express.py b/layouts/arrange/app-layout-columns-col-widths-express.py
new file mode 100644
index 00000000..75707465
--- /dev/null
+++ b/layouts/arrange/app-layout-columns-col-widths-express.py
@@ -0,0 +1,11 @@
+from shiny.express import ui
+
+ui.page_opts(fillable=True)
+
+with ui.layout_columns(col_widths=(2, 4, 6)):
+ with ui.card():
+ "Card 1"
+ with ui.card():
+ "Card 2"
+ with ui.card():
+ "Card 3"
diff --git a/layouts/arrange/app-layout-columns-core.py b/layouts/arrange/app-layout-columns-core.py
new file mode 100644
index 00000000..0c723bde
--- /dev/null
+++ b/layouts/arrange/app-layout-columns-core.py
@@ -0,0 +1,17 @@
+from shiny import App, ui
+
+app_ui = ui.page_fillable(
+ "ui.layout_columns()",
+ ui.layout_columns(
+ ui.card("Card 1"),
+ ui.card("Card 2"),
+ ui.card("Card 3"),
+ ),
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server=server)
diff --git a/layouts/arrange/app-layout-columns-express.py b/layouts/arrange/app-layout-columns-express.py
new file mode 100644
index 00000000..ec1d3b0e
--- /dev/null
+++ b/layouts/arrange/app-layout-columns-express.py
@@ -0,0 +1,13 @@
+from shiny.express import ui
+
+ui.page_opts(fillable=True)
+
+"ui.layout_columns()"
+
+with ui.layout_columns():
+ with ui.card():
+ "Card 1"
+ with ui.card():
+ "Card 2"
+ with ui.card():
+ "Card 3"
diff --git a/layouts/arrange/app-page-fixed-core.py b/layouts/arrange/app-page-fixed-core.py
new file mode 100644
index 00000000..a17597ee
--- /dev/null
+++ b/layouts/arrange/app-page-fixed-core.py
@@ -0,0 +1,26 @@
+import matplotlib.pyplot as plt
+import numpy as np
+from shiny import App, render, ui
+
+app_ui = ui.page_fixed( # <<
+ ui.layout_sidebar(
+ ui.panel_sidebar(
+ ui.input_slider("n", "N", 0, 100, 20),
+ ),
+ ui.panel_main(
+ ui.output_plot("histogram"),
+ ),
+ ),
+) # <<
+
+
+def server(input, output, session):
+ @output
+ @render.plot(alt="A histogram")
+ def histogram():
+ np.random.seed(19680801)
+ x = 100 + 15 * np.random.randn(437)
+ plt.hist(x, input.n(), density=True)
+
+
+app = App(app_ui, server, debug=True)
diff --git a/layouts/arrange/app-page-fixed-express.py b/layouts/arrange/app-page-fixed-express.py
new file mode 100644
index 00000000..47c7c879
--- /dev/null
+++ b/layouts/arrange/app-page-fixed-express.py
@@ -0,0 +1,13 @@
+import matplotlib.pyplot as plt
+import numpy as np
+from shiny.express import input, render, ui
+
+with ui.sidebar():
+ ui.input_slider("n", "N", 0, 100, 20)
+
+
+@render.plot(alt="A histogram")
+def histogram():
+ np.random.seed(19680801)
+ x = 100 + 15 * np.random.randn(437)
+ plt.hist(x, input.n(), density=True)
diff --git a/layouts/arrange/app-page-fluid-core.py b/layouts/arrange/app-page-fluid-core.py
new file mode 100644
index 00000000..95d04701
--- /dev/null
+++ b/layouts/arrange/app-page-fluid-core.py
@@ -0,0 +1,26 @@
+import matplotlib.pyplot as plt
+import numpy as np
+from shiny import App, render, ui
+
+app_ui = ui.page_fluid( # <<
+ ui.layout_sidebar(
+ ui.panel_sidebar(
+ ui.input_slider("n", "N", 0, 100, 20),
+ ),
+ ui.panel_main(
+ ui.output_plot("histogram"),
+ ),
+ ),
+) # <<
+
+
+def server(input, output, session):
+ @output
+ @render.plot(alt="A histogram")
+ def histogram():
+ np.random.seed(19680801)
+ x = 100 + 15 * np.random.randn(437)
+ plt.hist(x, input.n(), density=True)
+
+
+app = App(app_ui, server, debug=True)
diff --git a/layouts/arrange/app-page-fluid-express.py b/layouts/arrange/app-page-fluid-express.py
new file mode 100644
index 00000000..47c7c879
--- /dev/null
+++ b/layouts/arrange/app-page-fluid-express.py
@@ -0,0 +1,13 @@
+import matplotlib.pyplot as plt
+import numpy as np
+from shiny.express import input, render, ui
+
+with ui.sidebar():
+ ui.input_slider("n", "N", 0, 100, 20)
+
+
+@render.plot(alt="A histogram")
+def histogram():
+ np.random.seed(19680801)
+ x = 100 + 15 * np.random.randn(437)
+ plt.hist(x, input.n(), density=True)
diff --git a/layouts/arrange/index.qmd b/layouts/arrange/index.qmd
new file mode 100644
index 00000000..7952ba1a
--- /dev/null
+++ b/layouts/arrange/index.qmd
@@ -0,0 +1,267 @@
+---
+title: "Arrange Elements"
+description: >
+ Layout elements into rows and columns that responsively adapt to a wide range of
+ screen sizes.
+
+aliases:
+ - "../arrange.html"
+
+listing:
+- id: relevant-functions
+ template: ../../components/_partials/components-detail-relevant-functions.ejs
+ contents:
+ - title: ui.layout_columns
+ href: https://shiny.posit.co/py/api/ui.layout_columns.html
+ signature: ui.layout_columns(*args, col_widths=None, row_heights=None, fill=True, fillable=True, gap=None, class_=None, height=None, **kwargs)
+ - title: ui.layout_column_wrap
+ href: https://shiny.posit.co/py/api/ui.layout_column_wrap.html
+ signature: ui.layout_columns(*args, col_widths=None, row_heights=None, fill=True, fillable=True, gap=None, class_=None, height=None, **kwargs)
+ - title: ui.page_fixed
+ href: https://shiny.posit.co/py/api/ui.page_fixed.html
+ signature: ui.page_fixed(*args, title=None, lang=None, **kwargs)
+ - title: ui.page_fillable
+ href: https://shiny.posit.co/py/api/ui.page_fillable.html
+ signature: ui.page_fillable(*args, padding=None, gap=None, fillable_mobile=False, title=None, lang=None, **kwargs)
+---
+
+```{python}
+#| include: false
+import sys
+sys.path.append("../..")
+
+from docs.helpers import express_core_preview, shinylive_app_preview
+```
+
+Shiny provides two core functions for arranging elements into rows and columns:
+
+- `ui.layout_columns()` uses [Bootstrap's 12-column CSS Grid](https://getbootstrap.com/docs/5.3/layout/grid/) to create responsive and highly customizable layouts;
+
+- `ui.layout_column_wrap()` arranges elements into a grid with equally-sized cells.
+
+You can use these functions directly to create your own layout.
+Grid layouts can be used within a page, panel, or card and can even be nested within each other.
+
+::: {#relevant-functions}
+:::
+
+::: {.border-bottom .blue .mt-6 .mb-5}
+:::
+
+## Grid Layouts
+
+Both `ui.layout_columns()` and `ui.layout_column_wrap()` follow the same general pattern: pass each function a series of elements to have them arranged into a grid layout.
+
+::: {.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-layout-columns-core.py", viewer_height=175, div_attrs='.p-0 .resize-inline')
+express_core_preview("app-layout-columns-express.py", "app-layout-columns-core.py")
+```
+
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-layout-column-wrap-core.py", viewer_height=175, div_attrs='.p-0 .resize-inline')
+express_core_preview("app-layout-column-wrap-express.py", "app-layout-column-wrap-core.py")
+```
+:::
+
+Notice two things about the above apps:
+
+1. Both approaches create one or more rows, filling the rows with columns to hold its children.
+
+2. Use the handle in the bottom right corner of the apps above to view each layout at different screen sizes (this works best on desktop screens).
+ How do the layouts shift as the screen gets smaller or larger?
+
+### How to choose between `layout_columns()` and `layout_column_wrap()`
+
+For simple layouts, it can be hard to tell `ui.layout_columns()` and `ui.layout_column_wrap()` apart.
+Here's a short set of questions to ask yourself to help you decide which function to use:
+
+1. Are all of the items in your arrangement the same, such as a set of cards or plots?
+ And do you want them all to have the same width and height?
+
+ **If yes**: `ui.layout_column_wrap()` is best for a regularly-sized grid for a homogenous collection of elements.
+
+2. Do you want to customize the layout's column widths, or to have different layouts at different screen sizes (mobile vs desktop vs ultrawide)?
+
+ **If yes:** `ui.layout_columns()` give you the tools to create highly customized, resposive column layouts.
+
+3. For all other uses, pick the function you like the most.
+ I happen to like `ui.layout_columns()` but you might prefer `ui.layout_column_wrap()`.
+
+### Responsive, configurable column layouts
+
+In the example above, we saw that, without your intervention, `ui.layout_columns()` will arrange the elements given to it into evenly sized columns.
+In the default case, `ui.layout_columns()` makes a choice about the column widths that make the most sense based on the number of items provided.
+
+For example, when given three items, `ui.layout_columns()` places each item in a column with 4 width units.
+You can use the `col_widths` argument to choose a different allocation of column width units.
+Keep in mind that [Bootstrap's CSS Grid uses 12 units per row](https://getbootstrap.com/docs/5.3/layout/grid/).
+In the example below, we use `col_widths=(2, 4, 6)` to create a row with a small, medium, and large card.
+
+::: {.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-layout-columns-col-widths-core.py", viewer_height=240, div_attrs=".p-0 .resize-inline")
+express_core_preview("app-layout-columns-col-widths-express.py", "app-layout-columns-col-widths-core.py")
+```
+:::
+
+::: callout-tip
+#### Advanced layouts
+
+There's a lot more that `layout_columns()` can do with `col_widths` to make highly customized layouts.
+
+- Choose different layouts with a dictionary of column widths at specific breakpoints, e.g. `col_widths={"sm": 4, "md": (2, 4, 6)}`.
+
+- Insert empty space between items with a negative column width, e.g. `col_widths=c(4, -4, 4)` creates two columns 4 units wide with an empty space of 4 units between them.
+
+Learn more in the API reference: [Express](https://shiny.posit.co/py/api/express/ui.layout_columns.html) \| [Core](https://shiny.posit.co/py/api/core/ui.layout_columns.html)
+:::
+
+::: {.border-bottom .blue .mt-6 .mb-5}
+:::
+
+### Uniform grid layouts
+
+`ui.layout_column_wrap()` arranges elements into a uniform grid, so its `width` argument applies a single value to all elements in the grid.
+
+When `width` is a fractional each item is given an equal portion of the row's width.
+For example, `width=1 / 2` gives each item half of the row width.
+
+::: {.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-layout-column-wrap-half-core.py", viewer_height=240, div_attrs=".p-0 .resize-inline")
+express_core_preview("app-layout-column-wrap-half-express.py", "app-layout-column-wrap-half-core.py")
+```
+:::
+
+When `width` is a CSS unit, like `"300px"`, `ui.layout_column_wrap()` ensures that every item is *at least as wide* as `width`.
+The items in the grid expand to fill horizontal space or contract when the layout container is smaller than `width`.
+
+Try resizing the app preview below to see how the layout shifts at different container widths.
+
+::: {.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-layout-column-wrap-dynamic-core.py", viewer_height=240, div_attrs='.p-0 .resize-inline style="--max-width:1300px"')
+express_core_preview("app-layout-column-wrap-dynamic-express.py", "app-layout-column-wrap-dynamic-core.py")
+```
+:::
+
+## Column nesting
+
+Both `ui.layout_columns()` and `ui.layout_column_wrap()` can be nested, even within each other.
+
+Consider this page layout:
+
+::: {.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-column-nest-core.py", viewer_height=200, div_attrs=".p-0")
+express_core_preview("app-column-nest-express.py", "app-column-nest-core.py")
+```
+:::
+
+::: {.border-bottom .blue .mt-6 .mb-5}
+:::
+
+## Controlling for page width and height
+
+There are multiple types of Shiny grids: fluid, fixed, and fillable.
+The fluid grid system is recommended for most applications and is the default for Shiny functions like `ui.page_navbar()` and `ui.page_sidebar()`.
+So far, all the examples on this page have used the fluid grid system.
+
+Each grid system uses a flexibly sub-dividable 12-column grid for layout.
+They differ in how they interact with the viewer's browser window:
+
+- The fluid system always occupies the full width of the web page and re-sizes its components dynamically as the size of the page changes.
+
+- The fixed system occupies a fixed width of 940 pixels by default and may assume other widths when Bootstrap's responsive layout kicks in (e.g., when on a tablet).
+
+- The fillable system always occupies the full width *and height* of the web page and re-sizes its components dynamically as the size of the page changes.
+
+{.pt-4}
+
+### Fluid Grid System
+
+::: {.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-page-fluid-core.py", viewer_height=500, div_attrs=".p-0")
+express_core_preview("app-page-fluid-express.py", "app-page-fluid-core.py")
+```
+:::
+
+To make a fluid grid that adapts to the width of the user's viewing window, build your app UI with [ui.page_fluid](https://shiny.posit.co/py/api/ui.page_fluid.html).
+
+### Fixed Grid System
+
+::: {.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-page-fixed-core.py", viewer_height=500, div_attrs=".p-0")
+express_core_preview("app-page-fixed-express.py", "app-page-fixed-core.py")
+```
+:::
+
+To make a fixed grid that maintains a constant maximum width, build your app UI with [ui.page_fluid](https://shiny.posit.co/py/api/ui.page_fluid.html).
+
+The fixed grid system maintains a fixed page width of 940 pixels by default.
+If Bootstrap responsive features are enabled (they are by default in Shiny) then the grid will also adapt to be 724px or 1170px wide depending on your viewport (e.g., when on a tablet).
+The fixed system does this by using Bootstrap `.container`s, whereas the fluid system uses Bootstrap `.container-fluid`s.
+Learn more about the difference [here](https://getbootstrap.com/docs/5.3/layout/containers/#how-they-work).
+
+The main benefit of a fixed grid is that it provides stronger guarantees about how users will see the various elements of your UI laid out (because it's not being dynamically laid out according to the width of the browser).
+The main drawback is that a fixed grid is more complex to work with.
+In general, we recommend using a fluid grid unless you absolutely require the lower level layout control afforded by a fixed grid.
+
+### Fillable Grid System
+
+::: {.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-fillable-core.py", viewer_height=425, div_attrs=".p-0")
+express_core_preview("app-fillable-express.py", "app-fillable-core.py")
+```
+:::
+
+To make a fixed grid that maintains a constant maximum width, build your app UI with [ui.page_fillable](https://shiny.posit.co/py/api/ui.page_fillable.html).
+
+`ui.page_fluid()` and `ui.page_fixed()` create web pages that are laid out from the top down, leaving whitespace at the bottom if the page content's height is smaller than the browser window, and scrolling if the content is larger than the window.
+
+`ui.page_fillable()` is designed to latch the document body's size to the size of the window.
+This makes it possible to fill it with content that also scales to the size of the window.
+
+For example, `ui.page_fluid(ui.output_plot("plot", height="100%"))` will not work as expected.
+The plot element's effective height will be 0 because the plot's containing elements (`
` and ``) have automatic height.
+They determine their own height based on the height of their contained elements.
+However, `ui.page_fillable(ui.output_plot("plot", height="100%"))` will work as expected because `ui.page_fillable()` fixes the `` height at 100% of the window height.
+
+Note that `ui.page_fillable(ui.output_plot("plot"))` may not cause the plot to fill the page.
+Like most Shiny output widgets, `ui.output_plot()`'s default height is a fixed number of pixels.
+You must explicitly set `height="100%"` if you want a plot (or htmlwidget, say) to fill its container.
+
+One must be careful what layouts/panels/elements come between the `ui.page_fillable()` and the plots/widgets.
+Any container that has an automatic height will cause children with `height="100%"` to misbehave.
+Stick to functions that are designed for fill layouts, such as the ones in the Shiny package.
diff --git a/layouts/index.qmd b/layouts/index.qmd
index 67078ef7..454a9e9c 100644
--- a/layouts/index.qmd
+++ b/layouts/index.qmd
@@ -2,14 +2,17 @@
pagetitle: "Shiny Layouts"
anchor-sections: false
sidebar: false
-pagedescription: Frameworks that will allow the simplest and most complicated app to be useable and scalable. Is it bursting at the seams with content? Quickly change the layout for a fresh start.
+pagedescription: >
+ Frameworks that will allow the simplest and most complicated app to be useable and
+ scalable. Is it bursting at the seams with content? Quickly change the layout for a
+ fresh start.
toc: false
page-layout: article
resources:
- _partials/layouts.lottie
format:
html:
- css:
+ css:
- _partials/layouts.css
- _partials/layouts-list.css
code-overflow: wrap
@@ -27,7 +30,7 @@ Shiny Layouts
:::: {.g-col-md-5 .g-col-10 .g-start-4 .components-hero-img-container}
-
+
@@ -44,15 +47,15 @@ Shiny Layouts
:::: {.g-col-xl-3 .g-col-12}
::: {.sticky-xl-top .pt-4}
-
A navbar adds a navigation bar to your app, allowing users to easily navigate your app.
-
Learn Navbars
+
Learn Navbars
:::
::::
:::: {.g-col-xl-9 .g-col-12 .pt-3 .pt-xl-5 .ps-0 .ps-xl-5 .ms-0 .ms-xl-4}
@@ -60,17 +63,17 @@ A navbar adds a navigation bar to your app, allowing users to easily navigate yo
-

+

@@ -78,17 +81,17 @@ A navbar adds a navigation bar to your app, allowing users to easily navigate yo
-

+

@@ -106,15 +109,15 @@ A navbar adds a navigation bar to your app, allowing users to easily navigate yo
::: {.sticky-xl-top .pt-4}
-
-
+
+
Sidebars
-
+
A sidebar layout creates a sidebar, typically used for inputs, and a large main area, typically used for outputs.
-
Learn Sidebars
+
Learn Sidebars
:::
::::
@@ -125,17 +128,17 @@ A sidebar layout creates a sidebar, typically used for inputs, and a large main
-

+

@@ -143,52 +146,52 @@ A sidebar layout creates a sidebar, typically used for inputs, and a large main
-

+

-
+
-

+

-

+

@@ -204,15 +207,15 @@ A sidebar layout creates a sidebar, typically used for inputs, and a large main
::: {.sticky-xl-top .pt-4}
-
-
+
+
Tabs
-
+
Tabs and navigation allow you to create apps that have multiple pages.
-
Learn Tabs
+
Learn Tabs
:::
::::
:::: {.g-col-xl-9 .g-col-12 .pt-3 .pt-xl-5 .ps-0 .ps-xl-5 .ms-0 .ms-xl-4}
@@ -222,17 +225,17 @@ Tabs and navigation allow you to create apps that have multiple pages.
-

+

@@ -240,34 +243,34 @@ Tabs and navigation allow you to create apps that have multiple pages.
-

+

-

+

@@ -275,18 +278,18 @@ Tabs and navigation allow you to create apps that have multiple pages.
-

+

@@ -294,34 +297,34 @@ Tabs and navigation allow you to create apps that have multiple pages.
-

+

-

+

@@ -335,15 +338,15 @@ Tabs and navigation allow you to create apps that have multiple pages.
:::: {.g-col-xl-3 .g-col-12}
::: {.sticky-xl-top .pt-4}
-
-
+
+
Panels & Cards
-
+
Use panels and cards to define areas of related content.
-
Learn Panels & Cards
+
Learn Panels & Cards
:::
::::
:::: {.g-col-xl-9 .g-col-12 .pt-3 .pt-xl-5 .ps-0 .ps-xl-5 .ms-0 .ms-xl-4}
@@ -353,17 +356,17 @@ Use panels and cards to define areas of related content.
-

+

@@ -371,18 +374,18 @@ Use panels and cards to define areas of related content.
@@ -398,15 +401,15 @@ Use panels and cards to define areas of related content.
:::: {.g-col-xl-3 .g-col-12}
::: {.sticky-xl-top .pt-4}
-
-
+
+
Arrange Elements
-
+
Use rows and columns to create your own layout for every device size.
-
Arrange Elements
+
Arrange Elements
:::
::::
@@ -417,17 +420,17 @@ Use rows and columns to create your own layout for every device size.
-

+

@@ -435,35 +438,35 @@ Use rows and columns to create your own layout for every device size.
-

-
+

+
-
+
-

+

@@ -475,4 +478,4 @@ Use rows and columns to create your own layout for every device size.
::::::
::::::::
-
\ No newline at end of file
+
diff --git a/layouts/navbars.qmd b/layouts/navbars.qmd
deleted file mode 100644
index 02911535..00000000
--- a/layouts/navbars.qmd
+++ /dev/null
@@ -1,108 +0,0 @@
----
-title: "Navbars"
-description: "A navbar adds a navigation bar, allowing users to easily navigate your Shiny app."
----
-
-A navbar adds a navigation bar to your app, allowing users to easily navigate your app.
-
-## Relevant functions
-
-- [ui.page_navbar](https://shiny.posit.co/py/api/ui.page_navbar.html)
- `ui.page_navbar(*args, title=None, id=None, selected=None, sidebar=None, fillable=True, fillable_mobile=False, gap=None, padding=None, position='static-top', header=None, footer=None, bg=None, inverse=False, underline=True, collapsible=True, fluid=True, window_title=MISSING, lang=None)`
-- [ui.nav](https://shiny.posit.co/py/api/ui.nav.html)
- `ui.nav_panel(title, *args, value=None, icon=None)`
-
-:::{.border-bottom .blue .mt-6 .mb-5}
-:::
-
-
-## Navbar at top
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui
-
-app_ui = ui.page_navbar( #<<
- ui.nav_panel("A", "Page A content"),
- ui.nav_panel("B", "Page B content"),
- ui.nav_panel("C", "Page C content"),
- title="App with navbar", #<<
- id="navbar" #<<
-) #<<
-
-def server(input, output, session):
- pass
-
-app = App(app_ui, server)
-```
-
-
-
Edit in Shinylive
-
-:::
-
-Follow these steps to add a navbar to the top of your app:
-
- 1. Define a navbar page layout with `ui.page_navbar()`.
-
- 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.page_navbar()` to control the items displayed in the navbar.
-
- 3. Set the `title` argument of `ui.page_navbar()` to set the browser window title.
-
- 4. _Optional:_ Pass a string to the `id` argument of `ui.page_navbar()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
-
-:::{.border-bottom .blue .my-5}
-:::
-
-## Navbar at bottom
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui
-
-app_ui = ui.page_navbar( #<<
- ui.nav_panel("A", "Page A content"), #<<
- ui.nav_panel("B", "Page B content"), #<<
- ui.nav_panel("C", "Page C content"), #<<
- title="App with navbar", #<<
- id="navbar", #<<
- position="fixed-bottom", #<<
-) #<<
-
-def server(input, output, session):
- pass
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-:::
-
-Follow these steps to add a navbar to the bottom of your app:
-
- 1. Define a navbar page layout with `ui.page_navbar()`.
-
- 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.page_navbar()` to control the items displayed in the navbar.
-
- 3. Set the `position` parameter of `ui.page_navbar()` to `"fixed-bottom"` to pin the navbar to the bottom of the app. By default, `position` is `"static-top"`, which causes the navbar to display at the top with normal scrolling behavior. You can also pin the navbar to the top (`position="fixed-top"`).
-
- 4. Set the `title` argument of `ui.page_navbar()` to set the browser window title.
-
- 5. _Optional:_ Pass a string to the `id` argument of `ui.page_navbar()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
-
diff --git a/layouts/navbars/app-navbar-bottom-express.py b/layouts/navbars/app-navbar-bottom-express.py
new file mode 100644
index 00000000..e8fc860e
--- /dev/null
+++ b/layouts/navbars/app-navbar-bottom-express.py
@@ -0,0 +1,18 @@
+from functools import partial
+
+from shiny.express import ui
+from shiny.ui import page_navbar
+
+ui.page_opts(
+ title="App with navbar", # <<
+ page_fn=partial(page_navbar, id="page", position="fixed-bottom"), # <<
+)
+
+with ui.nav_panel("A"): # <<
+ "Page A content"
+
+with ui.nav_panel("B"): # <<
+ "Page B content"
+
+with ui.nav_panel("C"): # <<
+ "Page C content"
diff --git a/layouts/navbars/app-navbar-bottom.py b/layouts/navbars/app-navbar-bottom.py
new file mode 100644
index 00000000..5c5f6f95
--- /dev/null
+++ b/layouts/navbars/app-navbar-bottom.py
@@ -0,0 +1,17 @@
+from shiny import App, ui
+
+app_ui = ui.page_navbar( # <<
+ ui.nav_panel("A", "Page A content"), # <<
+ ui.nav_panel("B", "Page B content"), # <<
+ ui.nav_panel("C", "Page C content"), # <<
+ title="App with navbar", # <<
+ id="page", # <<
+ position="fixed-bottom", # <<
+) # <<
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/navbars/app-navbar-top-express.py b/layouts/navbars/app-navbar-top-express.py
new file mode 100644
index 00000000..fc11ba3b
--- /dev/null
+++ b/layouts/navbars/app-navbar-top-express.py
@@ -0,0 +1,18 @@
+from functools import partial
+
+from shiny.express import ui
+from shiny.ui import page_navbar
+
+ui.page_opts(
+ title="App with navbar", # <<
+ page_fn=partial(page_navbar, id="page"), # <<
+)
+
+with ui.nav_panel("A"): # <<
+ "Page A content"
+
+with ui.nav_panel("B"): # <<
+ "Page B content"
+
+with ui.nav_panel("C"): # <<
+ "Page C content"
diff --git a/layouts/navbars/app-navbar-top.py b/layouts/navbars/app-navbar-top.py
new file mode 100644
index 00000000..2484f935
--- /dev/null
+++ b/layouts/navbars/app-navbar-top.py
@@ -0,0 +1,16 @@
+from shiny import App, ui
+
+app_ui = ui.page_navbar( # <<
+ ui.nav_panel("A", "Page A content"), # <<
+ ui.nav_panel("B", "Page B content"), # <<
+ ui.nav_panel("C", "Page C content"), # <<
+ title="App with navbar", # <<
+ id="page", # <<
+) # <<
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/navbars/index.qmd b/layouts/navbars/index.qmd
new file mode 100644
index 00000000..9f9fd917
--- /dev/null
+++ b/layouts/navbars/index.qmd
@@ -0,0 +1,92 @@
+---
+title: "Navbars"
+description: >
+ A navbar adds a navigation bar, allowing users to easily navigate your
+ Shiny app.
+
+aliases:
+ - "../navbars.html"
+
+listing:
+- id: relevant-functions
+ template: ../../components/_partials/components-detail-relevant-functions.ejs
+ contents:
+ - title: ui.page_navbar
+ href: https://shiny.posit.co/py/api/ui.page_navbar.html
+ signature: ui.page_navbar(*args, title=None, id=None, selected=None, sidebar=None, fillable=True, fillable_mobile=False, gap=None, padding=None, position='static-top', header=None, footer=None, bg=None, inverse=False, underline=True, collapsible=True, fluid=True, window_title=MISSING, lang=None)
+ - title: ui.nav
+ href: https://shiny.posit.co/py/api/ui.nav.html
+ signature: ui.nav_panel(title, *args, value=None, icon=None)
+---
+
+```{python}
+#| include: false
+import sys
+sys.path.append("../..")
+
+from docs.helpers import express_core_preview, shinylive_app_preview
+```
+
+A navbar adds a navigation bar to your app, allowing users to easily navigate your app.
+
+:::{#relevant-functions}
+:::
+
+:::{.border-bottom .blue .mt-6 .mb-5}
+:::
+
+
+## Navbar at top
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-navbar-top.py", viewer_height=150, div_attrs=".p-0")
+express_core_preview("app-navbar-top-express.py", "app-navbar-top.py")
+```
+
+:::
+
+Follow these steps to add a navbar to the top of your app:
+
+ 1. Define a navbar page layout with `ui.page_navbar()`.
+
+ 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.page_navbar()` to control the items displayed in the navbar.
+
+ 3. Set the `title` argument of `ui.page_navbar()` to set the browser window title.
+
+ 4. _Optional:_ Pass a string to the `id` argument of `ui.page_navbar()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
+
+:::{.border-bottom .blue .my-5}
+:::
+
+## Navbar at bottom
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-navbar-bottom.py", viewer_height=150, div_attrs=".p-0")
+express_core_preview("app-navbar-bottom-express.py", "app-navbar-bottom.py")
+```
+
+:::
+
+
+Follow these steps to add a navbar to the bottom of your app:
+
+ 1. Define a navbar page layout with `ui.page_navbar()`.
+
+ 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.page_navbar()` to control the items displayed in the navbar.
+
+ 3. Set the `position` parameter of `ui.page_navbar()` to `"fixed-bottom"` to pin the navbar to the bottom of the app. By default, `position` is `"static-top"`, which causes the navbar to display at the top with normal scrolling behavior. You can also pin the navbar to the top (`position="fixed-top"`).
+
+ 4. Set the `title` argument of `ui.page_navbar()` to set the browser window title.
+
+ 5. _Optional:_ Pass a string to the `id` argument of `ui.page_navbar()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
+
diff --git a/layouts/panels-cards.qmd b/layouts/panels-cards.qmd
deleted file mode 100644
index 54857f55..00000000
--- a/layouts/panels-cards.qmd
+++ /dev/null
@@ -1,131 +0,0 @@
----
-title: "Panels and cards"
-description: "Use panels and cards to define areas of related content in your Shiny app."
----
-Use panels and cards to define areas of related content.
-
-## Relevant functions
-
-- [ui.card](https://shiny.posit.co/py/api/ui.card.html)
- `ui.card(*args, full_screen=False, height=None, max_height=None, min_height=None, fill=True, class_=None, **kwargs)`
-
-- [ui.card_footer](https://shiny.posit.co/py/api/ui.card_footer.html)
- `ui.card_footer(*args, **kwargs)`
-
-- [ui.card_header](https://shiny.posit.co/py/api/ui.card_header.html)
- `ui.card_header(*args, container=tags.div, **kwargs)`
-
-- [ui.panel_absolute](https://shiny.posit.co/py/api/ui.panel_absolute.html)
- `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](https://shiny.posit.co/py/api/ui.panel_fixed.html)
- `ui.panel_fixed(*args, **kwargs)`
-
-- [ui.panel_well](https://shiny.posit.co/py/api/ui.panel_well.html)
- `ui.panel_well(*args, **kwargs)`
-
-:::{.border-bottom .blue .mt-6 .mb-5}
-:::
-
-## Main image with panel floating above
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui
-
-app_ui = ui.page_fluid(
- ui.img( #<<
- src="https://source.unsplash.com/a-view-of-a-mountain-range-with-stars-in-the-sky-8QiI0ueYm3Q", #<<
- width="100%",#<<
- heigth="100%"#<<
- ),#<<
- ui.panel_absolute(#<<
- ui.panel_well(
- ui.panel_title("Draggable panel"),
- "Add elements here"
- ), #<<
- width="300px", #<<
- right="50px", #<<
- top="50px", #<<
- draggable=True #<<
- ), #<<
-)
-
-def server(input, output, session):
- pass
-
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-:::
-
-Follow these steps to create an app that has a panel floating over a main image.
-
-First, to create the image:
-
- 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`).
-
-Then, 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`.
-
-See also: [`ui.panel_fixed()`](https://shiny.posit.co/py/api/ui.panel_fixed.html). `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).
-
-:::{.border-bottom .blue .my-5}
-:::
-
-## Content divided by cards
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui, Inputs
-
-app_ui = ui.page_fluid(
- ui.card( #<<
- ui.card_header("Card 1 header"), #<<
- ui.p("Card 1 body"), #<<
- ui.input_slider("slider", "Slider", 0, 10, 5), #<<
- ), #<<
- ui.card( #<<
- ui.card_header("Card 2 header"), #<<
- ui.p("Card 2 body"), #<<
- ui.input_text("text", "Add text", "") #<<
- ), #<<
-)
-
-def server(input, output, session):
- pass
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-
-:::
-
-Cards are general purpose containers used to group related UI elements together with a border and optional padding.
-
-Follow these steps to create an app with content separated into cards:
-
- 1. Add `ui.card()` inside any Shiny UI page method (e.g., `ui.page_fluid()`) to create a card. Add additional cards by adding additional calls to `ui.card()`.
-
- 2. Define the contents of each card. Inside `ui.card()`, pass elements that you would like to appear inside the card (e.g., inputs, text). You can also use `ui.card_header()` and `ui.card_footer()` to create a card header and footer.
-
- 3. Control the appearance and functionality of each card by passing additional arguments to `ui.card()`. For example, you can set `full_screen` to `True` to allow the user to expand the card to fullscreen, or control the height of the card with the `height` parameter.
diff --git a/layouts/panels-cards/app-panel-absolute-core.py b/layouts/panels-cards/app-panel-absolute-core.py
new file mode 100644
index 00000000..cef090a5
--- /dev/null
+++ b/layouts/panels-cards/app-panel-absolute-core.py
@@ -0,0 +1,21 @@
+from shiny import App, ui
+
+app_ui = ui.page_fluid(
+ ui.panel_absolute( # <<
+ ui.panel_well(
+ ui.panel_title("Draggable panel"),
+ "Move this panel anywhere you want.",
+ ),
+ width="300px", # <<
+ right="50px", # <<
+ top="50px", # <<
+ draggable=True, # <<
+ ), # <<
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/panels-cards/app-panel-absolute-express.py b/layouts/panels-cards/app-panel-absolute-express.py
new file mode 100644
index 00000000..1a6dd230
--- /dev/null
+++ b/layouts/panels-cards/app-panel-absolute-express.py
@@ -0,0 +1,11 @@
+from shiny.express import ui
+
+with ui.panel_absolute(
+ width="300px", # <<
+ right="50px", # <<
+ top="50px", # <<
+ draggable=True, # <<
+): # <<
+ with ui.panel_well(): # <<
+ ui.h2("Draggable panel") # <<
+ "Move this panel anywhere you want." # <<
diff --git a/layouts/panels-cards/app-two-cards-core.py b/layouts/panels-cards/app-two-cards-core.py
new file mode 100644
index 00000000..208ae601
--- /dev/null
+++ b/layouts/panels-cards/app-two-cards-core.py
@@ -0,0 +1,23 @@
+from shiny import App, ui
+
+app_ui = ui.page_fillable(
+ ui.layout_columns( # <<
+ ui.card( # <<
+ ui.card_header("Card 1 header"),
+ ui.p("Card 1 body"),
+ ui.input_slider("slider", "Slider", 0, 10, 5),
+ ), # <<
+ ui.card( # <<
+ ui.card_header("Card 2 header"),
+ ui.p("Card 2 body"),
+ ui.input_text("text", "Add text", ""),
+ ), # <<
+ ) # <<
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/panels-cards/app-two-cards-express.py b/layouts/panels-cards/app-two-cards-express.py
new file mode 100644
index 00000000..2f832e92
--- /dev/null
+++ b/layouts/panels-cards/app-two-cards-express.py
@@ -0,0 +1,14 @@
+from shiny import ui
+
+ui.page_opts(fillable=True)
+
+with ui.layout_columns(): # <<
+ with ui.card(): # <<
+ ui.card_header("Card 1 header")
+ ui.p("Card 1 body")
+ ui.input_slider("slider", "Slider", 0, 10, 5)
+
+ with ui.card(): # <<
+ ui.card_header("Card 2 header")
+ ui.p("Card 2 body")
+ ui.input_text("text", "Add text", "")
diff --git a/layouts/panels-cards/index.qmd b/layouts/panels-cards/index.qmd
new file mode 100644
index 00000000..94a96370
--- /dev/null
+++ b/layouts/panels-cards/index.qmd
@@ -0,0 +1,107 @@
+---
+title: "Panels and cards"
+description: >
+ Use panels and cards to define areas of related content in your Shiny app.
+
+aliases:
+ - "../panels-cards.html"
+
+listing:
+- id: relevant-functions
+ template: ../../components/_partials/components-detail-relevant-functions.ejs
+ contents:
+ - title: ui.card
+ href: https://shiny.posit.co/py/api/ui.card.html
+ signature: ui.card(*args, full_screen=False, height=None, max_height=None, min_height=None, fill=True, class_=None, **kwargs)
+
+ - title: ui.card_footer
+ href: https://shiny.posit.co/py/api/ui.card_footer.html
+ signature: ui.card_footer(*args, **kwargs)
+
+ - title: ui.card_header
+ href: https://shiny.posit.co/py/api/ui.card_header.html
+ signature: ui.card_header(*args, container=tags.div, **kwargs)
+
+ - title: ui.panel_absolute
+ href: https://shiny.posit.co/py/api/ui.panel_absolute.html
+ signature: ui.panel_absolute(*args, top=None, left=None, right=None, bottom=None, width=None, height=None, draggable=False, fixed=False, cursor='auto', **kwargs)
+
+ - title: ui.panel_fixed
+ href: https://shiny.posit.co/py/api/ui.panel_fixed.html
+ signature: ui.panel_fixed(*args, **kwargs)
+
+ - title: ui.panel_well
+ href: https://shiny.posit.co/py/api/ui.panel_well.html
+ signature: ui.panel_well(*args, **kwargs)
+---
+
+```{python}
+#| include: false
+import sys
+sys.path.append("../..")
+
+from docs.helpers import express_core_preview, shinylive_app_preview
+```
+
+Use panels and cards to define areas of related content.
+
+:::{#relevant-functions}
+:::
+
+:::{.border-bottom .blue .mt-6 .mb-5}
+:::
+
+## Main image with panel floating above
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-panel-absolute-core.py", viewer_height=320, div_attrs=".p-0")
+express_core_preview("app-panel-absolute-express.py", "app-panel-absolute-core.py")
+```
+:::
+
+Follow these steps to create an app that has a panel floating over a main image.
+
+First, to create the image:
+
+ 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`).
+
+Then, 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`.
+
+See also: [`ui.panel_fixed()`](https://shiny.posit.co/py/api/ui.panel_fixed.html). `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).
+
+:::{.border-bottom .blue .my-5}
+:::
+
+## Content divided by cards
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-two-cards-core.py", viewer_height=320, div_attrs=".p-0")
+express_core_preview("app-two-cards-express.py", "app-two-cards-core.py")
+```
+:::
+
+Cards are general purpose containers used to group related UI elements together with a border and optional padding.
+
+Follow these steps to create an app with content separated into cards:
+
+ 1. Add `ui.card()` inside any Shiny UI page method (e.g., `ui.page_fluid()`) to create a card. Add additional cards by adding additional calls to `ui.card()`.
+
+ 2. Define the contents of each card. Inside `ui.card()`, pass elements that you would like to appear inside the card (e.g., inputs, text). You can also use `ui.card_header()` and `ui.card_footer()` to create a card header and footer.
+
+ 3. Control the appearance and functionality of each card by passing additional arguments to `ui.card()`. For example, you can set `full_screen` to `True` to allow the user to expand the card to fullscreen, or control the height of the card with the `height` parameter.
diff --git a/layouts/sidebars.qmd b/layouts/sidebars.qmd
deleted file mode 100644
index 9ed316f0..00000000
--- a/layouts/sidebars.qmd
+++ /dev/null
@@ -1,169 +0,0 @@
----
-title: "Sidebars"
-description: "A sidebar layout creates a sidebar in your Shiny app, typically used for inputs, and a large main area, typically used for outputs."
----
-
-A sidebar layout creates a sidebar, typically used for inputs, and a large main area, typically used for outputs.
-
-## Relevant functions
-
-- [ui.layout_sidebar](https://shiny.posit.co/py/api/ui.layout_sidebar.html)
- `ui.layout_sidebar(sidebar, *args, fillable=True, fill=True, bg=None, fg=None, border=None, border_radius=None, border_color=None, gap=None, padding=None, height=None, **kwargs)`
-
-- [ui.sidebar](https://shiny.posit.co/py/api/ui.sidebar.html#shiny.ui.sidebar)
- `ui.sidebar(*args, width=250, position='left', open='desktop', id=None, title=None, bg=None, fg=None, class_=None, max_height_mobile=None, gap=None, padding=None)`
-
-
-:::{.border-bottom .blue .mt-6 .mb-5}
-:::
-
-## Sidebar on the left
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui
-
-app_ui = ui.page_sidebar( #<<
- ui.sidebar("Sidebar", bg="#f8f8f8"), #<<
- "Main content"
-) #<<
-
-
-def server(input, output, session):
- pass
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-:::
-
-Follow these steps to add a left-side sidebar to your app:
-
- 1. Add `ui.layout_sidebar()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.layout_sidebar()` creates a sidebar layout.
-
- 2. Pass `ui.sidebar()`as the first argument of `ui.layout_sidebar()` to create the sidebar. By default, the `position` parameter of `ui.sidebar()` is "left" and the sidebar will appear on the left.
-
- 3. Supply additional components (output components, cards, text, etc.) to `ui.layout_sidebar()` to define the contents of the main content area.
-
-:::{.border-bottom .blue .my-5}
-:::
-
-## Sidebar on the right
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui
-
-app_ui = ui.page_sidebar ( #<<
- ui.sidebar("Sidebar", position="right", bg="#f8f8f8"), #<<
- "Main content"
-) #<<
-
-def server(input, output, session):
- pass
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-:::
-
-Follow these steps to add a right-side sidebar to your app:
-
- 1. Add `ui.layout_sidebar()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.layout_sidebar()` creates a sidebar layout.
-
- 2. Pass `ui.sidebar()`as the first argument of `ui.layout_sidebar()` to create the sidebar.
-
- 3. Pass `position="right"` to `ui.sidebar()`. The `position` argument controls where the sidebar appears relative to the main content.
-
- 3. Supply components (e.g., inputs) to `ui.sidebar()` to define the sidebar's contents. Supply additional components (e.g., output components, cards, etc.) to `ui.layout_sidebar()` to define the contents of the main content area.
-
-:::{.border-bottom .blue .my-5}
-:::
-
-## Sidebar within a card
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui
-
-app_ui = ui.page_fluid(
- ui.card( #<<
- ui.layout_sidebar( #<<
- ui.sidebar("Sidebar", bg="#f8f8f8"), #<<
- "Card content",
- ), #<<
- ) #<<
-)
-
-def server(input, output, session):
- pass
-
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-:::
-
-Follow these steps to add a sidebar within a card to your app:
-
- 1. Add `ui.card()` to the UI of your app to create a card.
- 2. Pass `ui.layout_sidebar()` to `ui.card()` to define a sidebar layout within the card.
- 3. Add `ui.sidebar()` and additional elements to `ui.layout_sidebar()` to define the sidebar and main content as usual.
- 4. Add inputs or other components as desired to `ui.sidebar()` to define the sidebar's contents.
-
-:::{.border-bottom .blue .my-5}
-:::
-
-## Collapsed sidebar
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui
-
-app_ui = ui.page_sidebar(
- ui.sidebar("Sidebar", bg="#f8f8f8", open="closed"), #<<
- "Main content"
-)
-
-
-def server(input, output, session):
- pass
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-:::
-
-
-`ui.sidebar()` has an `open` parameter that defines whether the sidebar appears open or closed when the app launches. To create a sidebar that is initially closed, set the `open` parameter to `"closed"`.
-
-The other options for `open` are:
-
- * `"desktop"`: The default. The sidebar starts open on a desktop screen and closed on mobile.
- * `"open"`: The sidebar starts open and can be closed.
- * `"always"`: The sidebar is always open and cannot be closed.
-
diff --git a/layouts/sidebars/app-sidebar-card-core.py b/layouts/sidebars/app-sidebar-card-core.py
new file mode 100644
index 00000000..3435fe84
--- /dev/null
+++ b/layouts/sidebars/app-sidebar-card-core.py
@@ -0,0 +1,18 @@
+from shiny import App, ui
+
+app_ui = ui.page_fillable(
+ ui.card( # <<
+ ui.card_header("Card with sidebar"),
+ ui.layout_sidebar( # <<
+ ui.sidebar("Sidebar", bg="#f8f8f8"), # <<
+ "Card content", # <<
+ ), # <<
+ ) # <<
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/sidebars/app-sidebar-card-express.py b/layouts/sidebars/app-sidebar-card-express.py
new file mode 100644
index 00000000..d0f6c327
--- /dev/null
+++ b/layouts/sidebars/app-sidebar-card-express.py
@@ -0,0 +1,12 @@
+from shiny.express import ui
+
+ui.page_opts(fillable=True)
+
+with ui.card(): # <<
+ ui.card_header("Card with sidebar")
+
+ with ui.layout_sidebar(): # <<
+ with ui.sidebar(bg="#f8f8f8"): # <<
+ "Sidebar" # <<
+
+ "Card content" # <<
diff --git a/layouts/sidebars/app-sidebar-collapsed-core.py b/layouts/sidebars/app-sidebar-collapsed-core.py
new file mode 100644
index 00000000..237cdae1
--- /dev/null
+++ b/layouts/sidebars/app-sidebar-collapsed-core.py
@@ -0,0 +1,13 @@
+from shiny import App, ui
+
+app_ui = ui.page_sidebar(
+ ui.sidebar("Sidebar", bg="#f8f8f8", open="closed"), # <<
+ "Main content",
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/sidebars/app-sidebar-collapsed-express.py b/layouts/sidebars/app-sidebar-collapsed-express.py
new file mode 100644
index 00000000..28f85802
--- /dev/null
+++ b/layouts/sidebars/app-sidebar-collapsed-express.py
@@ -0,0 +1,6 @@
+from shiny.express import ui
+
+with ui.sidebar(open="closed", bg="#f8f8f8"): # <<
+ "Sidebar"
+
+"Main content"
diff --git a/layouts/sidebars/app-sidebar-left-core.py b/layouts/sidebars/app-sidebar-left-core.py
new file mode 100644
index 00000000..22d04be6
--- /dev/null
+++ b/layouts/sidebars/app-sidebar-left-core.py
@@ -0,0 +1,13 @@
+from shiny import App, ui
+
+app_ui = ui.page_sidebar( # <<
+ ui.sidebar("Sidebar", bg="#f8f8f8"), # <<
+ "Main content", # <<
+) # <<
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/sidebars/app-sidebar-left-express.py b/layouts/sidebars/app-sidebar-left-express.py
new file mode 100644
index 00000000..642bd670
--- /dev/null
+++ b/layouts/sidebars/app-sidebar-left-express.py
@@ -0,0 +1,6 @@
+from shiny.express import ui
+
+with ui.sidebar(bg="#f8f8f8"): # <<
+ "Sidebar" # <<
+
+"Main content" # <<
diff --git a/layouts/sidebars/app-sidebar-right-core.py b/layouts/sidebars/app-sidebar-right-core.py
new file mode 100644
index 00000000..ae5c68fb
--- /dev/null
+++ b/layouts/sidebars/app-sidebar-right-core.py
@@ -0,0 +1,13 @@
+from shiny import App, ui
+
+app_ui = ui.page_sidebar( # <<
+ ui.sidebar("Sidebar", position="right", bg="#f8f8f8"), # <<
+ "Main content",
+) # <<
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/sidebars/app-sidebar-right-express.py b/layouts/sidebars/app-sidebar-right-express.py
new file mode 100644
index 00000000..d43bf43b
--- /dev/null
+++ b/layouts/sidebars/app-sidebar-right-express.py
@@ -0,0 +1,6 @@
+from shiny.express import ui
+
+with ui.sidebar(position="right", bg="#f8f8f8"): # <<
+ "Sidebar" # <<
+
+"Main content"
diff --git a/layouts/sidebars/index.qmd b/layouts/sidebars/index.qmd
new file mode 100644
index 00000000..303d3f7c
--- /dev/null
+++ b/layouts/sidebars/index.qmd
@@ -0,0 +1,141 @@
+---
+title: "Sidebars"
+description: >
+ A sidebar layout creates a sidebar in your Shiny app, typically used for inputs, and a
+ large main area, typically used for outputs.
+
+aliases:
+ - "../sidebars.html"
+
+listing:
+- id: relevant-functions
+ template: ../../components/_partials/components-detail-relevant-functions.ejs
+ contents:
+ - title: ui.layout_sidebar
+ href: https://shiny.posit.co/py/api/ui.layout_sidebar.html
+ signature: ui.layout_sidebar(sidebar, *args, fillable=True, fill=True, bg=None, fg=None, border=None, border_radius=None, border_color=None, gap=None, padding=None, height=None, **kwargs)
+
+ - title: ui.sidebar
+ href: https://shiny.posit.co/py/api/ui.sidebar.html#shiny.ui.sidebar
+ signature: ui.sidebar(*args, width=250, position='left', open='desktop', id=None, title=None, bg=None, fg=None, class_=None, max_height_mobile=None, gap=None, padding=None)
+---
+
+```{python}
+#| include: false
+import sys
+sys.path.append("../..")
+
+from docs.helpers import express_core_preview, shinylive_app_preview
+```
+
+A sidebar layout creates a sidebar, typically used for inputs, and a large main area, typically used for outputs.
+
+::: {#relevant-functions}
+:::
+
+
+:::{.border-bottom .blue .mt-6 .mb-5}
+:::
+
+## Sidebar on the left
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-sidebar-left-core.py", viewer_height=125, div_attrs=".p-0")
+express_core_preview("app-sidebar-left-express.py", "app-sidebar-left-core.py")
+```
+
+:::
+
+Follow these steps to add a left-side sidebar to your app:
+
+ 1. Add `ui.layout_sidebar()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.layout_sidebar()` creates a sidebar layout.
+
+ 2. Pass `ui.sidebar()`as the first argument of `ui.layout_sidebar()` to create the sidebar. By default, the `position` parameter of `ui.sidebar()` is "left" and the sidebar will appear on the left.
+
+ 3. Supply additional components (output components, cards, text, etc.) to `ui.layout_sidebar()` to define the contents of the main content area.
+
+:::{.border-bottom .blue .my-5}
+:::
+
+## Sidebar on the right
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-sidebar-right-core.py", viewer_height=125, div_attrs=".p-0")
+express_core_preview("app-sidebar-right-express.py", "app-sidebar-right-core.py")
+```
+
+:::
+
+
+Follow these steps to add a right-side sidebar to your app:
+
+ 1. Add `ui.layout_sidebar()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.layout_sidebar()` creates a sidebar layout.
+
+ 2. Pass `ui.sidebar()`as the first argument of `ui.layout_sidebar()` to create the sidebar.
+
+ 3. Pass `position="right"` to `ui.sidebar()`. The `position` argument controls where the sidebar appears relative to the main content.
+
+ 3. Supply components (e.g., inputs) to `ui.sidebar()` to define the sidebar's contents. Supply additional components (e.g., output components, cards, etc.) to `ui.layout_sidebar()` to define the contents of the main content area.
+
+:::{.border-bottom .blue .my-5}
+:::
+
+## Sidebar within a card
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-sidebar-card-core.py", viewer_height=175, div_attrs=".p-0")
+express_core_preview("app-sidebar-card-express.py", "app-sidebar-card-core.py")
+```
+
+:::
+
+
+Follow these steps to add a sidebar within a card to your app:
+
+ 1. Add `ui.card()` to the UI of your app to create a card.
+ 2. Pass `ui.layout_sidebar()` to `ui.card()` to define a sidebar layout within the card.
+ 3. Add `ui.sidebar()` and additional elements to `ui.layout_sidebar()` to define the sidebar and main content as usual.
+ 4. Add inputs or other components as desired to `ui.sidebar()` to define the sidebar's contents.
+
+:::{.border-bottom .blue .my-5}
+:::
+
+## Collapsed sidebar
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-sidebar-collapsed-core.py", viewer_height=125, div_attrs=".p-0")
+express_core_preview("app-sidebar-collapsed-express.py", "app-sidebar-collapsed-core.py")
+```
+
+:::
+
+
+
+`ui.sidebar()` has an `open` parameter that defines whether the sidebar appears open or closed when the app launches. To create a sidebar that is initially closed, set the `open` parameter to `"closed"`.
+
+The other options for `open` are:
+
+ * `"desktop"`: The default. The sidebar starts open on a desktop screen and closed on mobile.
+ * `"open"`: The sidebar starts open and can be closed.
+ * `"always"`: The sidebar is always open and cannot be closed.
+
diff --git a/layouts/tabs.qmd b/layouts/tabs.qmd
deleted file mode 100644
index b4cf7180..00000000
--- a/layouts/tabs.qmd
+++ /dev/null
@@ -1,330 +0,0 @@
----
-title: "Tabs"
-description: "Tabs and navigation allow you to create Shiny apps with multiple pages."
----
-
-Tabs and navigation allow you to create apps that have multiple pages.
-
-## Relevant functions
-
-- [ui.accordion](https://shiny.posit.co/py/api/ui.accordion.html#shiny.ui.accordion)
- `ui.accordion(*args, id=None, open=None, multiple=True, class_=None, width=None, height=None, **kwargs)`
-
-- [ui.accordion_panel](https://shiny.posit.co/py/api/ui.accordion_panel.html)
- `ui.accordion_panel(title, *args, value=MISSING, icon=None, **kwargs)`
-
-- [ui.navset_card_tab](https://shiny.posit.co/py/api/ui.navset_card_tab.html)
- `ui.navset_card_tab(*args, id=None, selected=None, title=None, sidebar=None, header=None, footer=None)`
-
-- [ui.navset_card_pill](https://shiny.posit.co/py/api/ui.navset_card_pill.html)
- `ui.navset_card_pill(*args, id=None, selected=None, title=None, sidebar=None, header=None, footer=None, placement='above')`
-
-- [ui.navset_pill](https://shiny.posit.co/py/api/ui.navset_pill.html)
- `ui.navset_pill(*args, id=None, selected=None, header=None, footer=None)`
-
-- [ui.navset_pill_list](https://shiny.posit.co/py/api/ui.navset_pill_list.html)
- `ui.navset_pill_list(*args, id=None, selected=None, header=None, footer=None, well=True, widths=(4, 8))`
-
-- [ui.navset_tab](https://shiny.posit.co/py/api/ui.navset_tab.html)
- `ui.navset_tab(*args, id=None, selected=None, header=None, footer=None)`
-
-:::{.border-bottom .blue .mt-6 .mb-5}
-:::
-
-## Tabset with pill navigation
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui
-
-app_ui = ui.page_fluid(
- ui.navset_pill( #<<
- ui.nav_panel("A", "Page A content"),
- ui.nav_panel("B", "Page B content"),
- ui.nav_panel("C", "Page C content"),
- ui.nav_menu(
- "Other links",
- ui.nav_panel("D", "Page D content"),
- "----", #<<
- "Description:",
- ui.nav_control(
- ui.a("Weblink", href="URL", target="_blank") #<<
- ),
- ),
- id = "tab" #<<
- ) #<<
-)
-
-def server(input, output, session):
- pass
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-:::
-
-Follow these steps to create an app with a tabset with pill navigation layout:
-
- 1. Add `ui.navset_pill()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_pill()` creates a pillset.
-
- 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_pill()` to set the items displayed in the navset.
-
- 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active.
-
- 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_pill()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
-
-:::{.border-bottom .blue .my-5}
-:::
-
-## Tabset with pill list navigation
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui
-
-app_ui = ui.page_fluid(
- ui.navset_pill_list( #<<
- ui.nav_panel("A", "Page A content"),
- ui.nav_panel("B", "Page B content"),
- ui.nav_panel("C", "Page C content"),
- ui.nav_menu(
- "Other links",
- ui.nav_panel("D", "Page D content"),
- "----",
- "Description:",
- ui.nav_control(
- ui.a("Weblink", href="URL", target="_blank")
- ),
- ),
- id = "tab" #<<
- ) #<<
-)
-
-def server(input, output, session):
- pass
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-:::
-
-Follow these steps to create an app with a pill list navigation layout. A pill list is a vertical pillset navigation.
-
- 1. Add `ui.navset_pill_list()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_pill()` creates a pill list.
-
- 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_pill_list()` to set the items displayed in the pillset.
-
- 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active.
-
- 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_pill_list()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
-
-:::{.border-bottom .blue .my-5}
-:::
-
-## Tabset with tab navigation
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-```{.python filename="app.py" }
-
-from shiny import App, ui
-
-app_ui = ui.page_fluid(
- ui.navset_tab( #<<
- ui.nav_panel("A", "Page A content"),
- ui.nav_panel("B", "Page B content"),
- ui.nav_panel("C", "Page C content"),
- ui.nav_menu(
- "Other links",
- ui.nav_panel("D", "Page D content"),
- "----",
- "Description:",
- ui.nav_control(
- ui.a("Weblink", href="URL", target="_blank")
- ),
- ),
- id = "tab" #<<
- ) #<<
-)
-
-def server(input, output, session):
- pass
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-:::
-
-Follow these steps to create an app with a tab navigation layout:
-
- 1. Add `ui.navset_tab()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_tab()` creates a tabset.
-
- 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_tab()` to set the items displayed in the tabset.
-
- 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active.
-
- 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_tab()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
-
-:::{.border-bottom .blue .my-5}
-:::
-
-## Card with a tabbed tabset
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui
-
-app_ui = ui.page_fluid(
- ui.navset_card_tab( #<<
- ui.nav_panel("A", "Page A content"),
- ui.nav_panel("B", "Page B content"),
- ui.nav_panel("C", "Page C content"),
- ui.nav_menu(
- "Other links",
- ui.nav_panel("D", "Page D content"),
- "----",
- "Description:",
- ui.nav_control(
- ui.a("Weblink", href="URL", target="_blank")
- ),
- ),
- id = "tab" #<<
- ) #<<
-)
-
-def server(input, output, session):
- pass
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-:::
-
-Follow these steps to add a card with a tabbed tabset to your app:
-
- 1. Add `ui.navset_card_tab()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_card_tab()` creates a tabset inside a card.
-
- 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_card_tab()` to set the items displayed in the tabset inside the card.
-
- 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active.
-
- 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_card_tab()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
-
-:::{.border-bottom .blue .my-5}
-:::
-
-## Card with a pill tabset
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui
-
-app_ui = ui.page_fluid(
- ui.navset_card_pill( #<<
- ui.nav_panel("A", "Page A content"),
- ui.nav_panel("B", "Page B content"),
- ui.nav_panel("C", "Page C content"),
- ui.nav_menu(
- "Other links",
- ui.nav_panel("D", "Page D content"),
- "----",
- "Description:",
- ui.nav_control(
- ui.a("Weblink", href="URL", target="_blank")
- ),
- ),
- id = "tab" #<<
- ) #<<
-)
-
-def server(input, output, session):
- pass
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-:::
-
-Follow these steps to add a card with a pill tabset to your app:
-
- 1. Add `ui.navset_card_pill()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_card_pill()` creates a pillset inside a card.
-
- 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_card_pill()` to set the items displayed in the pillset inside the card.
-
- 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active.
-
- 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_card_pill()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
-
-:::{.border-bottom .blue .my-5}
-:::
-
-## Vertically collapsing accordion panels
-
-:::{.column-screen-inset-right style="max-width:800px;"}
-
-
-
-
-```{.python filename="app.py" }
-from shiny import App, ui
-
-app_ui = ui.page_fluid(
- ui.accordion( #<<
- ui.accordion_panel("Section A", "Section A content"), #<<
- ui.accordion_panel("Section B", "Section B content"), #<<
- ui.accordion_panel("Section C", "Section C content"), #<<
- ui.accordion_panel("Section D", "Section D content"), #<<
- ui.accordion_panel("Section E", "Section E content"), #<<
- id="acc", #<<
- open = "Section A" #<<
- ), #<<
-)
-
-def server(input, output, session):
- pass
-
-app = App(app_ui, server)
-```
-
-
Edit in Shinylive
-:::
-
-Follow these steps to add vertically collapsing accordion panels to your app:
-
- 1. Add `ui.accordion()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.accordion()` creates a vertically collapsing accordion.
-
- 2. Pass accordion panel items to `ui.accordion()` using calls to `ui.accordion_panel()`. Each call to `ui.accordion_panel()` creates one accordion panel.
-
- 3. Pass arguments to the accordion panels to control each panel's title, appearance, and associated content. The `*args` argument of `ui.accordion_panel()` controls the contents of the accordion panel.
-
- 4. _Optional:_ Pass a string to the `id` argument of `ui.accordian()`. This will create an input value that represents the currently active accordion panels. For example, `id = "panel"` would create a reactive value accessible as `input.panel()`.
diff --git a/layouts/tabs/app-accordion-core.py b/layouts/tabs/app-accordion-core.py
new file mode 100644
index 00000000..51658f92
--- /dev/null
+++ b/layouts/tabs/app-accordion-core.py
@@ -0,0 +1,20 @@
+from shiny import App, ui
+
+app_ui = ui.page_fluid(
+ ui.accordion( # <<
+ ui.accordion_panel("Section A", "Section A content"), # <<
+ ui.accordion_panel("Section B", "Section B content"), # <<
+ ui.accordion_panel("Section C", "Section C content"), # <<
+ ui.accordion_panel("Section D", "Section D content"), # <<
+ ui.accordion_panel("Section E", "Section E content"), # <<
+ id="acc", # <<
+ open="Section A", # <<
+ ), # <<
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/tabs/app-accordion-express.py b/layouts/tabs/app-accordion-express.py
new file mode 100644
index 00000000..5beffb0a
--- /dev/null
+++ b/layouts/tabs/app-accordion-express.py
@@ -0,0 +1,17 @@
+from shiny.express import ui
+
+with ui.accordion(id="acc", open="Section A"): # <<
+ with ui.accordion_panel("Section A"): # <<
+ "Section A content"
+
+ with ui.accordion_panel("Section B"): # <<
+ "Section B content"
+
+ with ui.accordion_panel("Section C"): # <<
+ "Section C content"
+
+ with ui.accordion_panel("Section D"): # <<
+ "Section D content"
+
+ with ui.accordion_panel("Section E"): # <<
+ "Section E content"
diff --git a/layouts/tabs/app-tabset-pill-card-core.py b/layouts/tabs/app-tabset-pill-card-core.py
new file mode 100644
index 00000000..c96208d5
--- /dev/null
+++ b/layouts/tabs/app-tabset-pill-card-core.py
@@ -0,0 +1,26 @@
+from shiny import App, ui
+
+app_ui = ui.page_fillable(
+ ui.navset_card_pill( # <<
+ ui.nav_panel("A", "Panel A content"),
+ ui.nav_panel("B", "Panel B content"),
+ ui.nav_panel("C", "Panel C content"),
+ ui.nav_menu(
+ "Other links",
+ ui.nav_panel("D", "Panel D content"),
+ "----",
+ "Description:",
+ ui.nav_control(
+ ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
+ ),
+ ),
+ id="tab",
+ ) # <<
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/tabs/app-tabset-pill-card-express.py b/layouts/tabs/app-tabset-pill-card-express.py
new file mode 100644
index 00000000..f1ac6e32
--- /dev/null
+++ b/layouts/tabs/app-tabset-pill-card-express.py
@@ -0,0 +1,22 @@
+from shiny.express import ui
+
+ui.page_opts(fillable=True)
+
+with ui.navset_card_pill(id="tab"): # <<
+ with ui.nav_panel("A"):
+ "Panel A content"
+
+ with ui.nav_panel("B"):
+ "Panel B content"
+
+ with ui.nav_panel("C"):
+ "Panel C content"
+
+ with ui.nav_menu("Other links"):
+ with ui.nav_panel("D"):
+ "Page D content"
+
+ "----"
+ "Description:"
+ with ui.nav_control():
+ ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
diff --git a/layouts/tabs/app-tabset-pill-list-core.py b/layouts/tabs/app-tabset-pill-list-core.py
new file mode 100644
index 00000000..ef67cc04
--- /dev/null
+++ b/layouts/tabs/app-tabset-pill-list-core.py
@@ -0,0 +1,26 @@
+from shiny import App, ui
+
+app_ui = ui.page_fluid(
+ ui.navset_pill_list( # <<
+ ui.nav_panel("A", "Panel A content"),
+ ui.nav_panel("B", "Panel B content"),
+ ui.nav_panel("C", "Panel C content"),
+ ui.nav_menu(
+ "Other links",
+ ui.nav_panel("D", "Panel D content"),
+ "----",
+ "Description:",
+ ui.nav_control(
+ ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
+ ),
+ ),
+ id="tab",
+ ) # <<
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/tabs/app-tabset-pill-list-express.py b/layouts/tabs/app-tabset-pill-list-express.py
new file mode 100644
index 00000000..5747d8f8
--- /dev/null
+++ b/layouts/tabs/app-tabset-pill-list-express.py
@@ -0,0 +1,20 @@
+from shiny.express import ui
+
+with ui.navset_pill_list(id="tab"): # <<
+ with ui.nav_panel("A"):
+ "Panel A content"
+
+ with ui.nav_panel("B"):
+ "Panel B content"
+
+ with ui.nav_panel("C"):
+ "Panel C content"
+
+ with ui.nav_menu("Other links"):
+ with ui.nav_panel("D"):
+ "Page D content"
+
+ "----"
+ "Description:"
+ with ui.nav_control():
+ ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
diff --git a/layouts/tabs/app-tabset-pills-core.py b/layouts/tabs/app-tabset-pills-core.py
new file mode 100644
index 00000000..18b8374f
--- /dev/null
+++ b/layouts/tabs/app-tabset-pills-core.py
@@ -0,0 +1,26 @@
+from shiny import App, ui
+
+app_ui = ui.page_fluid(
+ ui.navset_pill( # <<
+ ui.nav_panel("A", "Panel A content"),
+ ui.nav_panel("B", "Panel B content"),
+ ui.nav_panel("C", "Panel C content"),
+ ui.nav_menu(
+ "Other links",
+ ui.nav_panel("D", "Panel D content"),
+ "----",
+ "Description:",
+ ui.nav_control(
+ ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
+ ),
+ ),
+ id="tab", # <<
+ ) # <<
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/tabs/app-tabset-pills-express.py b/layouts/tabs/app-tabset-pills-express.py
new file mode 100644
index 00000000..6e96d099
--- /dev/null
+++ b/layouts/tabs/app-tabset-pills-express.py
@@ -0,0 +1,20 @@
+from shiny.express import ui
+
+with ui.navset_pill(id="tab"): # <<
+ with ui.nav_panel("A"):
+ "Panel A content"
+
+ with ui.nav_panel("B"):
+ "Panel B content"
+
+ with ui.nav_panel("C"):
+ "Panel C content"
+
+ with ui.nav_menu("Other links"):
+ with ui.nav_panel("D"):
+ "Page D content"
+
+ "----"
+ "Description:"
+ with ui.nav_control():
+ ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
diff --git a/layouts/tabs/app-tabset-tab-card-core.py b/layouts/tabs/app-tabset-tab-card-core.py
new file mode 100644
index 00000000..61ba8c60
--- /dev/null
+++ b/layouts/tabs/app-tabset-tab-card-core.py
@@ -0,0 +1,26 @@
+from shiny import App, ui
+
+app_ui = ui.page_fillable(
+ ui.navset_card_tab( # <<
+ ui.nav_panel("A", "Panel A content"),
+ ui.nav_panel("B", "Panel B content"),
+ ui.nav_panel("C", "Panel C content"),
+ ui.nav_menu(
+ "Other links",
+ ui.nav_panel("D", "Panel D content"),
+ "----",
+ "Description:",
+ ui.nav_control(
+ ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
+ ),
+ ),
+ id="tab", # <<
+ ) # <<
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/tabs/app-tabset-tab-card-express.py b/layouts/tabs/app-tabset-tab-card-express.py
new file mode 100644
index 00000000..6df58f28
--- /dev/null
+++ b/layouts/tabs/app-tabset-tab-card-express.py
@@ -0,0 +1,22 @@
+from shiny.express import ui
+
+ui.page_opts(fillable=True)
+
+with ui.navset_card_tab(id="tab"): # <<
+ with ui.nav_panel("A"):
+ "Panel A content"
+
+ with ui.nav_panel("B"):
+ "Panel B content"
+
+ with ui.nav_panel("C"):
+ "Panel C content"
+
+ with ui.nav_menu("Other links"):
+ with ui.nav_panel("D"):
+ "Page D content"
+
+ "----"
+ "Description:"
+ with ui.nav_control():
+ ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
diff --git a/layouts/tabs/app-tabset-tab-core.py b/layouts/tabs/app-tabset-tab-core.py
new file mode 100644
index 00000000..4f393972
--- /dev/null
+++ b/layouts/tabs/app-tabset-tab-core.py
@@ -0,0 +1,26 @@
+from shiny import App, ui
+
+app_ui = ui.page_fluid(
+ ui.navset_tab( # <<
+ ui.nav_panel("A", "Panel A content"),
+ ui.nav_panel("B", "Panel B content"),
+ ui.nav_panel("C", "Panel C content"),
+ ui.nav_menu(
+ "Other links",
+ ui.nav_panel("D", "Panel D content"),
+ "----",
+ "Description:",
+ ui.nav_control(
+ ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
+ ),
+ ),
+ id="tab", # <<
+ ) # <<
+)
+
+
+def server(input, output, session):
+ pass
+
+
+app = App(app_ui, server)
diff --git a/layouts/tabs/app-tabset-tab-express.py b/layouts/tabs/app-tabset-tab-express.py
new file mode 100644
index 00000000..6e96d099
--- /dev/null
+++ b/layouts/tabs/app-tabset-tab-express.py
@@ -0,0 +1,20 @@
+from shiny.express import ui
+
+with ui.navset_pill(id="tab"): # <<
+ with ui.nav_panel("A"):
+ "Panel A content"
+
+ with ui.nav_panel("B"):
+ "Panel B content"
+
+ with ui.nav_panel("C"):
+ "Panel C content"
+
+ with ui.nav_menu("Other links"):
+ with ui.nav_panel("D"):
+ "Page D content"
+
+ "----"
+ "Description:"
+ with ui.nav_control():
+ ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
diff --git a/layouts/tabs/index.qmd b/layouts/tabs/index.qmd
new file mode 100644
index 00000000..d08ff092
--- /dev/null
+++ b/layouts/tabs/index.qmd
@@ -0,0 +1,205 @@
+---
+title: "Tabs"
+description: >
+ Tabs and navigation allow you to create Shiny apps with multiple pages.
+
+aliases:
+ - "../tabs.html"
+
+listing:
+- id: relevant-functions
+ template: ../../components/_partials/components-detail-relevant-functions.ejs
+ contents:
+ - title: ui.accordion
+ href: https://shiny.posit.co/py/api/ui.accordion.html#shiny.ui.accordion
+ signature: ui.accordion(*args, id=None, open=None, multiple=True, class_=None, width=None, height=None, **kwargs)
+
+ - title: ui.accordion_panel
+ href: https://shiny.posit.co/py/api/ui.accordion_panel.html
+ signature: ui.accordion_panel(title, *args, value=MISSING, icon=None, **kwargs)
+
+ - title: ui.navset_card_tab
+ href: https://shiny.posit.co/py/api/ui.navset_card_tab.html
+ signature: ui.navset_card_tab(*args, id=None, selected=None, title=None, sidebar=None, header=None, footer=None)
+
+ - title: ui.navset_card_pill
+ href: https://shiny.posit.co/py/api/ui.navset_card_pill.html
+ signature: ui.navset_card_pill(*args, id=None, selected=None, title=None, sidebar=None, header=None, footer=None, placement='above')
+
+ - title: ui.navset_pill
+ href: https://shiny.posit.co/py/api/ui.navset_pill.html
+ signature: ui.navset_pill(*args, id=None, selected=None, header=None, footer=None)
+
+ - title: ui.navset_pill_list
+ href: https://shiny.posit.co/py/api/ui.navset_pill_list.html
+ signature: ui.navset_pill_list(*args, id=None, selected=None, header=None, footer=None, well=True, widths=(4, 8))
+
+ - title: ui.navset_tab
+ href: https://shiny.posit.co/py/api/ui.navset_tab.html
+ signature: ui.navset_tab(*args, id=None, selected=None, header=None, footer=None)
+---
+
+```{python}
+#| include: false
+import sys
+sys.path.append("../..")
+
+from docs.helpers import express_core_preview, shinylive_app_preview
+```
+
+Tabs and navigation allow you to create apps that have multiple pages.
+
+::: {#relevant-functions}
+:::
+
+:::{.border-bottom .blue .mt-6 .mb-5}
+:::
+
+## Tabset with pill navigation
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-tabset-pills-core.py", viewer_height=190, div_attrs=".p-0")
+express_core_preview("app-tabset-pills-express.py", "app-tabset-pills-core.py")
+```
+
+:::
+
+Follow these steps to create an app with a tabset with pill navigation layout:
+
+ 1. Add `ui.navset_pill()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_pill()` creates a pillset.
+
+ 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_pill()` to set the items displayed in the navset.
+
+ 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active.
+
+ 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_pill()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
+
+:::{.border-bottom .blue .my-5}
+:::
+
+## Tabset with pill list navigation
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-tabset-pill-list-core.py", viewer_height=320, div_attrs=".p-0")
+express_core_preview("app-tabset-pill-list-express.py", "app-tabset-pill-list-core.py")
+```
+:::
+
+Follow these steps to create an app with a pill list navigation layout. A pill list is a vertical pillset navigation.
+
+ 1. Add `ui.navset_pill_list()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_pill()` creates a pill list.
+
+ 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_pill_list()` to set the items displayed in the pillset.
+
+ 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active.
+
+ 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_pill_list()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
+
+:::{.border-bottom .blue .my-5}
+:::
+
+## Tabset with tab navigation
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-tabset-tab-core.py", viewer_height=200, div_attrs=".p-0")
+express_core_preview("app-tabset-tab-express.py", "app-tabset-tab-core.py")
+```
+:::
+
+Follow these steps to create an app with a tab navigation layout:
+
+ 1. Add `ui.navset_tab()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_tab()` creates a tabset.
+
+ 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_tab()` to set the items displayed in the tabset.
+
+ 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active.
+
+ 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_tab()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
+
+:::{.border-bottom .blue .my-5}
+:::
+
+## Card with a tabbed tabset
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-tabset-tab-card-core.py", viewer_height=250, div_attrs=".p-0")
+express_core_preview("app-tabset-tab-card-express.py", "app-tabset-tab-card-core.py")
+```
+:::
+
+Follow these steps to add a card with a tabbed tabset to your app:
+
+ 1. Add `ui.navset_card_tab()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_card_tab()` creates a tabset inside a card.
+
+ 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_card_tab()` to set the items displayed in the tabset inside the card.
+
+ 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active.
+
+ 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_card_tab()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
+
+:::{.border-bottom .blue .my-5}
+:::
+
+## Card with a pill tabset
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-tabset-pill-card-core.py", viewer_height=250, div_attrs=".p-0")
+express_core_preview("app-tabset-pill-card-express.py", "app-tabset-pill-card-core.py")
+```
+:::
+
+Follow these steps to add a card with a pill tabset to your app:
+
+ 1. Add `ui.navset_card_pill()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.navset_card_pill()` creates a pillset inside a card.
+
+ 2. Pass nav items (e.g., [`ui.nav_panel()`](https://shiny.posit.co/py/api/ui.nav.html) and [`ui.nav_menu()`](https://shiny.posit.co/py/api/ui.nav_menu.html)) to `ui.navset_card_pill()` to set the items displayed in the pillset inside the card.
+
+ 3. Pass arguments to the nav items to control each item's title, appearance, and associated content. For example, set the `title` argument of `ui.nav_panel()` to control the displayed title of the nav item. Pass UI elements as additional arguments to `ui.nav_panel()`. These elements will be displayed when the tab is active.
+
+ 4. _Optional:_ Pass a string to the `id` argument of `ui.navset_card_pill()`. This will create an input value that holds the title of the currently selected nav item. For example, `id = "tab"` would create a reactive value accessible as `input.tab()`.
+
+:::{.border-bottom .blue .my-5}
+:::
+
+## Vertically collapsing accordion panels
+
+:::{.column-screen-inset-right style="max-width:800px;"}
+```{python}
+#| output: asis
+#| echo: false
+
+shinylive_app_preview("app-accordion-core.py", viewer_height=350, div_attrs=".p-0")
+express_core_preview("app-accordion-express.py", "app-accordion-core.py")
+```
+:::
+
+Follow these steps to add vertically collapsing accordion panels to your app:
+
+ 1. Add `ui.accordion()` inside any Shiny UI page method (e.g., `ui.page_fluid()`). `ui.accordion()` creates a vertically collapsing accordion.
+
+ 2. Pass accordion panel items to `ui.accordion()` using calls to `ui.accordion_panel()`. Each call to `ui.accordion_panel()` creates one accordion panel.
+
+ 3. Pass arguments to the accordion panels to control each panel's title, appearance, and associated content. The `*args` argument of `ui.accordion_panel()` controls the contents of the accordion panel.
+
+ 4. _Optional:_ Pass a string to the `id` argument of `ui.accordian()`. This will create an input value that represents the currently active accordion panels. For example, `id = "panel"` would create a reactive value accessible as `input.panel()`.
diff --git a/py-shiny b/py-shiny
index 6023c66c..9ad2ab12 160000
--- a/py-shiny
+++ b/py-shiny
@@ -1 +1 @@
-Subproject commit 6023c66c48347466cb637f9d865c08a7aa7ba944
+Subproject commit 9ad2ab12ae0054ac745efed78da5fceb1e09fc43
diff --git a/quarto-style.scss b/quarto-style.scss
index 8e172028..f3fbb8e4 100644
--- a/quarto-style.scss
+++ b/quarto-style.scss
@@ -1710,15 +1710,40 @@ btn.action-button:hover {
}
}
+.app-preview {
+ padding: 1em;
+ border: 1px solid rgba(233,236,239,.9);
+ border-radius: 0.5em;
+ margin-block: 1em;
+
+ &.resize-inline,
+ &.resize-block {
+ .shinylive-viewer {
+ background-color: var(--bs-gray-800);
+ }
+ }
+
+ &.resize-inline .app-frame {
+ resize: inline;
+ min-width: var(--min-width, 200px);
+ max-width: var(--max-width, 100%);
+ }
+ &.resize-block .app-frame {
+ resize: block;
+ min-height: var(--min-height, 100px);
+ }
+}
+
.app-preview .sourceCode {
padding: 0;
box-shadow: none;
- margin-block: 1em;
+ margin: 0;
+ border: none;
}
.app-preview .shinylive-wrapper {
margin: 0;
- padding: 1em;
+ padding: 0;
background-color: var(--bs-body-bg);
}
diff --git a/requirements.txt b/requirements.txt
index 7fcc1746..cd5c9792 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
jupyter
jupyter_client < 8.0.0
tabulate
-shinylive @ git+https://github.com/posit-dev/py-shinylive.git
+shinylive==0.2.0
matplotlib==3.8.1
shiny
seaborn==0.13.0