Shinylive applications embedded in Quarto documents

Embedded Shiny application

To display a running Shiny app, use a code block with {shinylive-python}.

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

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

app_ui = ui.page_fluid(
    ui.layout_sidebar(
        ui.sidebar(
            ui.input_slider("period", "Period", 0.5, 4, 1, step=0.5),
            ui.input_slider("amplitude", "Amplitude", 0, 2, 1, step=0.25),
            ui.input_slider("shift", "Phase shift", 0, 2, 0, step=0.1),
        ),
        ui.output_plot("plot"),
    ),
)


def server(input, output, session):
    @output
    @render.plot(alt="Sine wave")
    def plot():
        t = np.arange(0.0, 4.0, 0.01)
        s = input.amplitude() * np.sin(
            2 * np.pi / input.period() * (t - input.shift() / 2)
        )
        fig, ax = plt.subplots()
        ax.set_ylim([-2, 2])
        ax.plot(t, s)
        ax.grid()


app = App(app_ui, server)

Note that the code block currently must have #| standalone: true, which indicates that the code represents a complete Shiny application, as opposed to one which has parts spread throughout the document (which will be supported in the future).

The example above also uses #| viewerHeight: 420 to set the height of the viewer to 420 pixels.

Editor with app

If you want to display an editor panel with along with the running application, use #| components: [editor, viewer]. Users will be able to use the editor to modify the code and re-run the application.

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

from shiny import App, render, ui

app_ui = ui.page_fluid(
    ui.input_slider("n", "N", 0, 100, 40),
    ui.output_text_verbatim("txt"),
)

def server(input, output, session):
    @output
    @render.text
    def txt():
        return f"The value of n*2 is {input.n() * 2}"

app = App(app_ui, server)

The default width in a Quarto document is somewhat narrow for showing the editor and viewer next to each other. It can be made wider with Quarto layout containers. In the example above it uses column-page-inset-right.

Vertically stacked components

To display the editor above the code, use #| layout: vertical.

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

from shiny import App, render, ui

app_ui = ui.page_fluid(
    ui.input_slider("n", "N", 0, 100, 40),
    ui.output_text_verbatim("txt"),
)

def server(input, output, session):
    @output
    @render.text
    def txt():
        return f"The value of n*2 is {input.n() * 2}"

app = App(app_ui, server)

Multiple files

For apps that have multiple files, you can mark the start of each file with ## file: filename.py.

Binary files can be used with ## type: binary, and the file must be base64-encoded. This can be done with the base64 command line tool.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
## file: app.py
from pathlib import Path
from shiny import App, render, ui, Inputs, Outputs, Session
from utils import square

app_ui = ui.page_fluid(
    ui.layout_columns(
        ui.input_slider("n", "Make a Shiny square:", min=0, max=6, value=2),
        ui.output_ui("images"),
    )
)

def server(input: Inputs, output: Outputs, session: Session):
    @output
    @render.ui
    def images():
        img = ui.img(src="logo.png", style="width: 40px;")
        return square(img, input.n())

www_dir = Path(__file__).parent / "www"
app = App(app_ui, server, static_assets=www_dir)

## file: utils.py
from shiny import ui

def square(x, n):
    row = ui.div([x] * n)
    return ui.div([row] * n)

## file: www/logo.png
## type: binary
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