Layout functions provide the high-level visual structure of an app. Layouts are created by a hierarchy of function calls, where the hierarchy in R matches the hierarchy in the generated HTML. This helps you understand layout code.
R Code 6.1 : Single page layout
Loading...
6.2.1 Page functions
The most important, but least interesting, layout function is fluidPage(), which you’ve seen in pretty much every example so far. But what’s it doing and what happens if you use it by itself? Figure 6.1 shows the results: it looks like a very boring app but there’s a lot going behind the scenes, because fluidPage() sets up all the HTML, CSS, and JavaScript that Shiny needs.
R Code 6.2 : An UI consisting just of fluidPage()
Loading...
In addition to fluidPage(), Shiny provides a couple of other page functions that can come in handy in more specialized situations: fixedPage() and fillPage().
fixedPage() works like fluidPage() but has a fixed maximum width, which stops your apps from becoming unreasonably wide on bigger screens.
fillPage() fills the full height of the browser and is useful if you want to make a plot that occupies the whole screen.
To make more complex layouts, you’ll need call layout functions inside of fluidPage(). For example, to make a two-column layout with inputs on the left and outputs on the right you can use sidebarLayout() (along with its friends titlePanel(), sidebarPanel(), and mainPanel()).
Resource 6.2 : Sidebar functions
sidebarLayout(): Layout a sidebar and main area. Create a layout (sidebarLayout()) with a sidebar (sidebarPanel()) and main area (mainPanel())
titlePanel(): Create a panel containing an application title.
I had to choose a slightly wider page width. Otherwise Shiny would have stacked the two panels.
R Code 6.4 : Layout example: Page with sidebar
Loading...
Rendering the app (or reloading the web page) results for a moment with the error message “figure margins too large”. I do not know why this happens and how to prevent it.
6.2.3 Multi-row
Under the hood, sidebarLayout() is built on top of a flexible multi-row layout, which you can use directly to create more visually complex apps. As usual, you start with fluidPage(). Then you create rows with fluidRow(), and columns with column().
Resource 6.3 : Multi-row functions
A fluid page layout consists of rows which in turn include columns.
fluidRow(): Rows exist for the purpose of making sure their elements appear on the same line (if the browser has adequate width).
column(): Columns exist for the purpose of defining how much horizontal space within a 12-unit wide grid it’s elements should occupy.
Each row is made up of 12 columns and the first argument to column() gives how many of those columns to occupy. A 12 column layout gives you substantial flexibility because you can easily create 2-, 3-, or 4-column layouts, or use narrow columns to create spacers. You can see an example of this layout in Section 4.4.
If you’d like to learn more about designing using a grid system, I highly recommend the classic text on the subject: “Grid systems in graphic design” by Josef Müller-Brockman (1998).
Screenshot 6.2: The structure underlying a simple multi-row app
6.2.4 Exercises
6.2.4.1sidebarLayout()
Read the documentation of sidebarLayout() to determine the width (in columns) of the sidebar and the main panel. Can you recreate its appearance using fluidRow() and column()? What are you missing?
Exercise 6.1 : sidebarLayout()
The sidbarbarPanel() has a width of 4 columns and the mainPanel() is eight columns wide. The 12 column grid of the standard page is divided 1/3 (controls) : 2/3 (output).
Loading...
Missing is the titlePanel().
6.2.4.2 Change panel positions
Modify the Central Limit Theorem app from Code Collection 6.1 to put the sidebar on the right instead of the left.
Exercise 6.2 : Central Limit Theorem with the sidebar on the right
Loading...
6.2.4.3 Stacked sidebarLayout()
Exercise 6.3 : Main panel divided 50:50 with sidebar below
Loading...
6.3 Multi-page layout
As your app grows in complexity, it might become impossible to fit everything on a single page. In this section you’ll learn various uses of tabPanel() that create the illusion of multiple pages. This is an illusion because you’ll still have a single app with a single underlying HTML file, but it’s now broken into pieces and only one piece is visible at a time.
Multi-page apps pair particularly well with modules, which you’ll learn about in (XXX_19?). Shiny modules allow you to partition up the server function in the same way you partition up the user interface, creating independent components that only interact through well defined connections.
6.3.1 Tabsets
The simple way to break up a page into pieces is to use tabsetPanel() and its close friend tabPanel(). As you can see in the code below, tabsetPanel() creates a container for any number of tabPanels(), which can in turn contain any other HTML components.
tabsetPanel() can be used anywhere in your app; it’s totally fine to nest tabsets inside of other components (including tabsets!) if needed.
Code Collection 6.2 : Using tabsets: tabsetPanel() and tabPanels()
R Code 6.6 : Tabset with location detection (via ID)
Loading...
6.3.2 Navlist and navbars
Because tabs are displayed horizontally, there’s a fundamental limit to how many tabs you can use, particularly if they have long titles. navbarPage() and navbarMenu() provide two alternative layouts that let you use more tabs with longer titles.
navlistPanel() is similar to tabsetPanel() but instead of running the tab titles horizontally, it shows them vertically in a sidebar. It also allows you to add headings with plain strings, as shown in the code below.
Another approach is the use of navbarPage(): it still runs the tab titles horizontally, but you can use navbarMenu() to add drop-down menus for an additional level of hierarchy.
Bootstrap is so ubiquitous within the R community that it’s easy to get style fatigue: after a while every Shiny app and Rmd start to look the same. The solution is theming with the {bslib} package. {bslib}1 is a relatively new package that allows you to override many Bootstrap defaults in order to create an appearance that is uniquely yours.
{bslib} has several advantages:
It is designed not only to work for Shiny but also for other contexts, like R Markdown.
It provides custom theming, even interactively in real-time.
It uses newer versions of Bootstrap and Bootswatch, whereas Shiny and R Markdown currently default to Bootstrap 3 and may continue to do so to maintain backwards compatibility.
Bootstrap is a free and open-source CSS framework designed for responsive, mobile-first front-end web development.
Bootswatch is a collection of pre-built themes that can be easily applied to a Bootstrap project, simplifying the process of achieving a polished and professional look.
6.5.1 Getting started
Create a theme with bslib::bs_theme() then apply it to an app with the theme argument of the page layout function:
fluidPage( theme = bslib::bs_theme(...))
If not specified, Shiny will use the classic Bootstrap v3 theme that it has used basically since it was created. By default, bslib::bs_theme(), will use Bootstrap v5. Using Bootstrap v5 instead of v3 will not cause problems if you only use built-in components. There is a possibility that it might cause problems if you’ve used custom HTML, so you can force it to stay with v3 with version = 3.
6.5.2 Shiny themes
The easiest way to change the overall look of your app is to pick a premade “bootswatch” theme using the bootswatch argument to bslib::bs_theme().
In my first trials I got always the error message “Text to be written must be a length-one character vector”. After googling the error message I found out that the error message comes from {htmltools}:
This error occurs in htmltools::WSTextWriter (see here and CTRL+F to look for “writeImpl”). This function is in charge of properly writing text to your display when you display a raw text in any UI function. It raises the mentioned error when the writeImpl() function receives more than one character string (aka element of a character() vector). To correct this, make sure you never provide two character strings to a Shiny UI function asking for only one character argument. (From Eli Ker Ano, the first comment under the StackOverflow question Text to be written must be a length-one character vector).
I tried to follow this advice but to no avail. Even if I deleted all lines between sidebarPanel() and mainPanel() the error persisted. Could it be that {bslib} has changed in the meanwhile and requires other layout commands? This means that it would not be fully compatible with Shiny anymore. That seems unlikely.
=instead of <- required for theme = bslib::bs_theme()
After several hours I finally found the problem. The <- operator works fine without additional parameters (allowed is only bootswatch = "<theme name>") but with (additional) other arguments you have to use the = operator.
I copied the code snippet from the book and that was wrong! In the code chunk of 6.5.1 Shiny Themes is the correct = operator used, but in the code snippet below not.
With shinylive in the Quarto document it works with the <- operator as well!
6.5.3 Plot themes
If you’ve heavily customized the style of your app, you may want to also customize your plots to match. Luckily, this is really easy thanks to the {thematic} package which automatically themes {ggplot2}, {lattice}, and base plots. Just call thematic::thematic_shiny() in your server function. This will automatically determine all of the settings from your app theme.
R Code 6.11 : Numbered R Code Title
6.5.4 Exercises
The suggested bslib::bs_themer() function worked in my case only with a native theme. Real-time theming worked but I had always to start with the fresh standard theme. I couldn’t succeed to incorporate already those changes that I have already made to the theme.
I learned two other live theme changing procedures that allows to start from an already specified theme:
bslib::bs_theme_preview(theme) as recommended in the book. It is important that you fix those theme changes that you are satisfied with. For this you could use the function theme = bslib::bs_theme(<here include the changes you already made>)
bslib::run_with_themer(shinyApp(ui, server)) as explained in Outstanding User Interfaces with Shiny. Replace the shinyApp(ui, server) code line with run_with_themer(shinyApp(ui, server)) and start the app. The run_with_themer() function allows you to work on the app and to modify the theme at the same time.
Watch out! 6.1: Compiled {curl} package for webR not available
I can’t run this app in shinyliv because there is no WASM compatible compilation of the {curl} package available.
An error has occurred!
Downloading Google Font files requires either the curl package or capabilities('libcurl').
Running capabilities('libcurl') in the console returns true but trying to install the packages in the webR demo page with
Warning message: In install.packages(“curl”, repos = c(“https://tidyverse.r-universe.dev”, : Requested package curl not found in webR binary repo.
In this special case the problem is connected with the download of Google font with font_google(<font name>). A way around the problem is to download the Google fonts and integrate them into the operating system. In that case shinylive have access to these fonts and downloading fonts with font_google(<font name>) is not necessary anymore.
For more information on the issue missing compiling WASM compatible R packages see Resource 9.5.
Resource 6.6 : How to download Google fonts and integrate them into macOS
To download and install Google font see the article by Junian Triajianto: How to Install ALL Google Fonts on macOS: A simple guide to install the entire Google Fonts library on macOS. He explains two different ways to accomplish the same: one procedure uses the terminal, the other not. Both are easy to understand and the written explanation is supported by two videos: One for the terminal way, the other without terminal.
R Code 6.12 : Real-time Shiny themes updates
6.6 Under the hood
There’s no magic behind all the input, output, and layout functions: they just generate HTML. (The magic is done by JavaScript, which is outside the scope of the book.)
You can see that HTML by executing UI functions directly in the console:
shiny::fluidPage( shiny::textInput("name", "What's your name?"))<div class="container-fluid"><div class="form-group shiny-input-container"><label for="name">What's your name?</label><input id="name" type="text" class="form-control" value=""/></div></div>
Note that this is the contents of the <body> tag; other parts of Shiny take care of generating the <head>. If you want to include additional CSS or JS dependencies you’ll need to learn htmltools::htmlDependency(). Two good places to start are https://blog.r-hub.io/2020/08/25/js-r/#web-dependency-management and https://unleash-shiny.rinterface.com/htmltools-dependencies.html.
It’s possible to add your own HTML to the ui. There are two different ways:
One way to do so is by including literal HTML with the htmltools::HTML() function. In the example below, the “raw character constant”, r"()" is used, to make it easier to include quotes in the string. You can even skip fluidPage() altogether and supply raw HTML for the whole UI. See Build your entire UI with HTML for more details.
Alternatively, you can make use of the HTML helper that Shiny provides via import of {htmltools} functions. There are regular functions for the most important elements like h1() and p(), and all others can be accessed via the other tags helper2. Named arguments become attributes and unnamed arguments become children, so we can recreate the above HTML with these regular functions.
R Code 6.13 : Add HTML raw code snippets to the Shiny UI
R Code 6.14 : Numbered R Code Title (Tidyverse)
One advantage of generating HTML with code is that you can interweave existing Shiny components into a custom structure. For example, the code below makes a paragraph of text containing two outputs, one which is bold:
tags$p( "You made ", tags$b("$", textOutput("amount", inline = TRUE)), " in the last ", textOutput("days", inline = TRUE), " days " )
Note the use of inline = TRUE; the textOutput() default is to produce a complete paragraph.
To learn more about using HTML, CSS, and JavaScript to make compelling user interfaces, I highly recommend David Granjon’s Outstanding User Interfaces with Shiny.
Resource 6.7 : HTML, CSS, and JavaScript to make compelling user interfaces
Granjon, David, Veerle van Leemput, Victor Perrier, and Isabelle Rudolf. 2024. “shinyMobile: Mobile Ready ’Shiny’ Apps with Standalone Capabilities.”https://doi.org/10.32614/CRAN.package.shinyMobile.
Mülller-Brockmann, Josef. 1998. Grid Systems in Graphic Design A Visual Communication Manual for Graphic Designers Typographers and Three Dimensional Designers Hardcover 1 Jan 1999. Generic.
Stachura, Filip, Dominik Krzeminski, Krystian Igras, Adam Forys, Paweł Przytuła, Jakub Chojna, Olga Mierzwa-Sulima, Jakub Nowicki, and Tymoteusz Makowski. 2024. “Shiny.semantic: Semantic UI Support for Shiny.”https://doi.org/10.32614/CRAN.package.shiny.semantic.
Żyła, Kamil, Jakub Nowicki, Leszek Siemiński, Marek Rogala, Recle Vibal, Tymoteusz Makowski, and Rodrigo Basa. 2025. “Rhino: A Framework for Enterprise Shiny Applications.”https://doi.org/10.32614/CRAN.package.rhino.
Therefore the name: bslib is an acronym for bootstrap library.↩︎
---execute: cache: true---# Layouts, themes, HTML {#sec-chap06}```{r}#| label: setup#| results: hold#| include: falselibrary(glossary)glossary::glossary_path("../glossary-pb/glossary.yml")library(shiny)```## Introduction:::::: {#obj-chap06}::::: my-objectives::: my-objectives-headerChapter section list:::::: my-objectives-container::::::::::::::## Single page layouts {#sec-06-single-page-layouts}Layout functions provide the high-level visual structure of an app.Layouts are created by a hierarchy of function calls, where thehierarchy in R matches the hierarchy in the generated HTML. This helpsyou understand layout code.:::::: my-r-code:::: my-r-code-header::: {#cnj-06-single-page-layout}: Single page layout:::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| viewerHeight: 400#| components: [editor, viewer]ui<- fluidPage(titlePanel("Hello Shiny!"),sidebarLayout(sidebarPanel(sliderInput("obs","Observations:", min = 0, max = 1000, value = 500)),mainPanel(plotOutput("distPlot"))))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::### Page functionsThe most important, but least interesting, layout function is`fluidPage()`, which you’ve seen in pretty much every example so far.But what’s it doing and what happens if you use it by itself? Figure 6.1shows the results: it looks like a very boring app but there’s a lotgoing behind the scenes, because `fluidPage()` sets up all the HTML,CSS, and JavaScript that Shiny needs.:::::: my-r-code:::: my-r-code-header::: {#cnj-06-fluid-page}: An UI consisting just of `fluidPage()`:::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| components: [editor, viewer]ui<- fluidPage()server<- function(input, output, session){}shinyApp(ui, server)```:::::::::In addition to `fluidPage()`, Shiny provides a couple of other pagefunctions that can come in handy in more specialized situations:`fixedPage()` and `fillPage()`.- `fixedPage()` works like `fluidPage()` but has a fixed maximum width, which stops your apps from becoming unreasonably wide on bigger screens.- `fillPage()` fills the full height of the browser and is useful if you want to make a plot that occupies the whole screen.:::::: my-resource:::: my-resource-header::: {#lem-06-fluid-page}: Page functions:::::::::: my-resource-container- [fluidPage()](https://shiny.posit.co/r/reference/shiny/latest/fluidpage.html): Create a page with fluid layout.- [fixedPage()](https://shiny.posit.co/r/reference/shiny/latest/fixedpage.html): Create a page with a fixed layout.- [fillPage()](https://shiny.posit.co/r/reference/shiny/latest/fillpage.html): Create a page that fills the window.:::::::::### Page with sidebarTo make more complex layouts, you’ll need call layout functions insideof `fluidPage()`. For example, to make a two-column layout with inputson the left and outputs on the right you can use `sidebarLayout()`(along with its friends `titlePanel()`, `sidebarPanel()`, and`mainPanel()`).:::::: my-resource:::: my-resource-header::: {#lem-06-page-with-sidebar}: Sidebar functions:::::::::: my-resource-container- [sidebarLayout()](https://shiny.posit.co/r/reference/shiny/latest/sidebarlayout.html): Layout a sidebar and main area. Create a layout (`sidebarLayout()`) with a sidebar (`sidebarPanel()`) and main area (`mainPanel()`)- [titlePanel()](https://shiny.posit.co/r/reference/shiny/latest/titlepanel.html): Create a panel containing an application title.:::::::::{#fig-06-01fig-alt="alt-text" fig-align="center" width="70%"}::::::::::::::::: column-body-outset:::::::::::::::: my-code-collection::::: my-code-collection-header::: my-code-collection-icon:::::: {#exm-06-page-sidebar}: Page with sidebar layout:::::::::::::::::::: my-code-collection-container::::::::::: panel-tabset###### Empty Layout:::::: my-r-code:::: my-r-code-header::: {#cnj-06-page-sidebar-empty}: Page with sidebar:::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| viewerHeight: 200#| components: [editor, viewer]#| layout: vertical# Define UIui<- fluidPage(titlePanel("Hello Shiny!"),sidebarLayout(sidebarPanel("sidebarPanel"),mainPanel("mainPanel")))# Server logicserver<- function(input, output){}# Complete app with UI and server componentsshinyApp(ui, server)```------------------------------------------------------------------------I had to choose a slightly wider page width. Otherwise Shiny would havestacked the two panels.:::::::::###### Example:::::: my-r-code:::: my-r-code-header::: {#cnj-06-page-sidebar-example}: Layout example: Page with sidebar:::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| viewerHeight: 500#| components: [editor, viewer]#| layout: verticalui<- fluidPage(titlePanel("Central limit theorem"),sidebarLayout(sidebarPanel(numericInput("m","Number of samples: (1-100)", 2, min = 1, max = 100),"Increase the number of samples to see the distribution become more normal."),mainPanel(plotOutput("hist"))))server<- function(input, output, session){output$hist<- renderPlot({req("m")means<- replicate(1e4, mean(runif(input$m)))hist(means, breaks = 20)}, res = 96)}shinyApp(ui, server)```------------------------------------------------------------------------Rendering the app (or reloading the web page)results for a moment withthe error message "figure margins too large". I do not know why thishappens and how to prevent it.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::### Multi-rowUnder the hood, `sidebarLayout()`is built on top of a flexiblemulti-row layout, which you can use directly to create more visuallycomplex apps. As usual, you start with `fluidPage()`. Then you createrows with `fluidRow()`, and columns with `column()`.:::::: my-resource:::: my-resource-header::: {#lem-06-multi-row-layout}: Multi-row functions:::::::::: my-resource-containerA fluid page layout consists of rows which in turn include columns.- [fluidRow()](https://shiny.posit.co/r/reference/shiny/latest/fluidpage.html):Rows exist for the purpose of making sure their elements appear onthe same line (ifthe browser has adequate width).- [column()](https://shiny.posit.co/r/reference/shiny/latest/column.html):Columns exist for the purpose of defining how much horizontal spacewithin a 12-unit wide grid it's elements should occupy.:::::::::Each row is made up of 12 columns and the first argument to `column()`gives how many of those columns to occupy. A 12 column layout gives yousubstantial flexibility because you can easily create 2-, 3-, or4-column layouts, or use narrow columns to create spacers. You can seean example of this layout in @sec-04-prototype.If you’d like to learn more about designing using a grid system, Ihighly recommend the classic text on the subject: “Grid systems ingraphic design” by Josef Müller-Brockman [-@mueller-brockmann-1998].{#fig-06-02fig-alt="alt-text" fig-align="center" width="70%"}### Exercises#### `sidebarLayout()`Read the documentation of `sidebarLayout()` to determine the width (incolumns) of the sidebar and the main panel. Can you recreate itsappearance using `fluidRow()` and `column()`? What are you missing?::::::::::::::: column-body-outset:::::: my-exercise:::: my-exercise-header::: {#exr-06-ex-01-sidebarlayout}: `sidebarLayout()`:::::::::: my-exercise-containerThe `sidbarbarPanel()` has a width of 4 columns and the `mainPanel()` iseight columns wide. The 12 column grid of the standard page is divided1/3 (controls) : 2/3 (output).```{shinylive-r}#| standalone: true#| viewerHeight: 100#| components: [editor, viewer]#| layout: verticalui <- fluidPage( fluidRow( column( width = 4, "place for the controls: 4 columns" ), column( width = 8, "place for the output: 8 columns" ) ))server <- function(input, output, session) {}shinyApp(ui, server)```------------------------------------------------------------------------Missing is the `titlePanel()`.:::::::::#### Change panel positionsModify the Central Limit Theorem app from @exm-06-page-sidebar to putthe sidebar on the right instead of the left.:::::: my-exercise:::: my-exercise-header::: {#exr-sidebar-right}: Central Limit Theorem with the sidebar on the right:::::::::: my-exercise-container```{shinylive-r}#| standalone: true#| viewerHeight: 500#| components: [editor, viewer]#| layout: verticalui <- fluidPage( titlePanel("Central limit theorem"), sidebarLayout( sidebarPanel( numericInput("m", "Number of samples: (1-100)", 2, min = 1, max = 100), "Increase the number of samples to see the distribution become more normal." ), mainPanel( plotOutput("hist") ), position = "right" ))server <- function(input, output, session) { output$hist <- renderPlot({ req("m") means <- replicate(1e4, mean(runif(input$m))) hist(means, breaks = 20) }, res = 96)}shinyApp(ui, server)```:::::::::#### Stacked `sidebarLayout()`:::::: my-exercise:::: my-exercise-header::: {#exr-06-ex-03}: Main panel divided 50:50 with sidebar below:::::::::: my-exercise-container```{shinylive-r}#| standalone: true#| viewerHeight: 500#| components: [editor, viewer]#| layout: verticalui <- fluidPage( title = "Central limit theorem", fluidRow( column(width = 6, plotOutput("hist1") ), column(width = 6, plotOutput("hist2") ), ), fluidRow( column(width = 6, numericInput("m1", "Number of samples: (1-100)", 2, min = 1, max = 100) ), column(width = 6, numericInput("m2", "Number of samples: (1-100)", 2, min = 1, max = 100) ) ))server <- function(input, output, session) { output$hist1 <- renderPlot({ means <- replicate(1e4, mean(runif(input$m1))) hist(means, breaks = 20) }, res = 96) output$hist2 <- renderPlot({ means <- replicate(1e4, mean(runif(input$m2))) hist(means, breaks = 20) }, res = 96)}shinyApp(ui, server)```::::::::::::::::::::::::## Multi-page layoutAs your app grows in complexity, it might become impossible to fiteverything on a single page. In this section you’ll learn various usesof `tabPanel()` that create the illusion of multiple pages. This is anillusion because you’ll still have a single app with a single underlyingHTML file, but it’s now broken into pieces and only one piece is visibleat a time.Multi-page apps pair particularly well with modules, which you’ll learnabout in @XXX_19. Shiny modules allow you to partition up the serverfunction in the same way you partition up the user interface, creatingindependent components that only interact through well definedconnections.### TabsetsThe simple way to break up a page into pieces is to use `tabsetPanel()`and its close friend `tabPanel()`. As you can see in the code below,`tabsetPanel()` creates a container for any number of `tabPanels()`,which can in turn contain any other HTML components.`tabsetPanel()` can be used anywhere in your app; it’s totally fine tonest tabsets inside of other components (including tabsets!) if needed.:::::::::::::::: my-code-collection::::: my-code-collection-header::: my-code-collection-icon:::::: {#exm-06-tabsets}: Using tabsets: `tabsetPanel()` and `tabPanels()`:::::::::::::::::::: my-code-collection-container::::::::::: panel-tabset###### Simple example:::::: my-r-code:::: my-r-code-header::: {#cnj-tabset-simple-example}: A simple tabset example:::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| viewerHeight: 400#| components: [editor, viewer]#| layout: verticalui <- fluidPage( tabsetPanel( tabPanel("Data", fileInput("file", "Data", buttonLabel = "Upload..."), textInput("delim", "Delimiter (leave blank to guess)", ""), numericInput("skip", "Rows to skip", 0, min = 0), numericInput("rows", "Rows to preview", 10, min = 1) ), tabPanel("Parameters"), tabPanel("Results") ))server <- function(input, output, session) {}shinyApp(ui, server)```:::::::::###### With ID:::::: my-r-code:::: my-r-code-header::: {#cnj-06-tabset-with-id}: Tabset with location detection (via ID):::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| viewerHeight: 200#| components: [editor, viewer]#| layout: verticalui <- fluidPage( sidebarLayout( sidebarPanel( textOutput("panel") ), mainPanel( tabsetPanel( id = "tabset", tabPanel("panel 1", "one"), tabPanel("panel 2", "two"), tabPanel("panel 3", "three") ) ) ))server <- function(input, output, session) { output$panel <- renderText({ paste("Current panel: ", input$tabset) })}shinyApp(ui, server)```::::::::::::::::::::::::::::::::::::::::::::::::### Navlist and navbarsBecause tabs are displayed horizontally, there’s a fundamental limit tohow many tabs you can use, particularly if they have long titles.`navbarPage()` and `navbarMenu()` provide two alternative layouts thatlet you use more tabs with longer titles.`navlistPanel()` is similar to `tabsetPanel()` but instead of runningthe tab titles horizontally, it shows them vertically in a sidebar. Italso allows you to add headings with plain strings, as shown in the codebelow.Another approach is the use of `navbarPage()`: it still runs the tabtitles horizontally, but you can use `navbarMenu()` to add drop-downmenus for an additional level of hierarchy.:::::::::::::::: my-code-collection::::: my-code-collection-header::: my-code-collection-icon:::::: {#exm-06-navbar}: Navlist and navbars:::::::::::::::::::: my-code-collection-container::::::::::: panel-tabset###### `navlistPanel()`:::::: my-r-code:::: my-r-code-header::: {#cnj-06-navlist-panel}: `navlistPanel()`: Tabs horizontally:::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| viewerHeight: 400#| components: [editor, viewer]library(shiny)ui <- fluidPage( navlistPanel( id = "tabset", "Heading 1", tabPanel("panel 1", "Panel one contents"), "Heading 2", tabPanel("panel 2", "Panel two contents"), tabPanel("panel 3", "Panel three contents") ))server <- function(input, output, session) {}shinyApp(ui, server)```:::::::::###### navbarPage():::::: my-r-code:::: my-r-code-header::: {#cnj-06-navbar-page}: `navbarPage()` with `navbarMenu()` for submenues:::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| viewerHeight: 400#| components: [editor, viewer]library(shiny)ui <- navbarPage( "Page title", tabPanel("panel 1", "one"), tabPanel("panel 2", "two"), tabPanel("panel 3", "three"), navbarMenu("subpanels", tabPanel("panel 4a", "four-a"), tabPanel("panel 4b", "four-b"), tabPanel("panel 4c", "four-c") ))server <- function(input, output, session) {}shinyApp(ui, server)```::::::::::::::::::::::::::::::::::::::::::::::::## Bootstrap`r glossary("Bootstrap")` is a collection of HTML conventions, CSSstyles, and JS snippets bundled up into a convenient form.It’s good to know that Bootstrap exists because then:- You can use `bslib::bs_theme()` to customize the visual appearance of your code, @sec-06-themes.- You can use the `class` argument to customize some layouts, inputs, and outputs using Bootstrap class names, as you saw in @sec-action-buttons.- You can make your own functions to generate Bootstrap components that Shiny doesn’t provide, as explained in “[Utility classes](https://rstudio.github.io/bslib/articles/utility-classes/index.html)”.:::::: my-resource:::: my-resource-header::: {#lem-shiny-css-frameworks}: Shiny CSS frameworks:::::::::: my-resource-container- [{**bslib**}](https://rstudio.github.io/bslib/index.html) by `r glossary("Positx", "Posit")` based on [Bootstrap](https://getbootstrap.com/) [@bslib].- [{**shiny.semantics**}](https://appsilon.github.io/shiny.semantic/) by `r glossary("Appsilon")`, builds on top of [Fomantic UI](https://fomantic-ui.com/) [@shiny.semantic]. Appsilon also developed [{**rhino**}](https://appsilon.github.io/rhino/index.html), an R package designed for professional developers to help building high quality, enterprise-grade Shiny applications at speed [@rhino].- [{**shinyMobile**}](https://shinymobile.rinterface.com/) by `r glossary("RInteRface")`, builds on top of [framework 7](https://framework7.io/), and is specifically designed for mobile apps [@shinyMobile].- [{**shinymaterial**}](https://ericrayanderson.github.io/shinymaterial/) by [Eric Anderson,](https://github.com/ericrayanderson) is built on top of [Google’s Material design framework](https://m3.material.io/).- [{**shinydashboard**}](https://rstudio.github.io/shinydashboard/) also by Posit, provides a layout system designed to create dashboards.You can find a fuller, and actively maintained, list at [Awesome ShinyExtension](https://github.com/nanxstats/awesome-shiny-extensions).:::::::::## Themes {#sec-06-themes}Bootstrap is so ubiquitous within the R community that it’s easy to getstyle fatigue: after a while every Shiny app and Rmd start to look thesame. The solution is theming with the {**bslib**} package.{**bslib**}[^06-layouts-1] is a relatively new package that allows youto override many Bootstrap defaults in order to create an appearancethat is uniquely yours.[^06-layouts-1]: Therefore the name: `bslib` is an acronym for **b**oot**s**trap **lib**rary.{**bslib**} has several advantages:- It is designed not only to work for Shiny but also for other contexts, like R Markdown.- It provides [custom theming](https://rstudio.github.io/bslib/articles/theming/index.html), even interactively in real-time.- It uses newer versions of `r glossary("Bootstrap")` and `r glossary("Bootswatch")`, whereas Shiny and `r glossary("RMarkdown", "R Markdown")` currently default to Bootstrap 3 and may continue to do so to maintain backwards compatibility.:::::: my-resource:::: my-resource-header::: {#lem-06-bslib-layout-design}: {bslib} layout design:::::::::: my-resource-container- {**bslib**} [package documentation](https://rstudio.github.io/bslib/index.html) for custom bootstrap `r glossary("SASS")` themes for Shiny and R Markdown- New Shiny [application layout guide](https://shiny.posit.co/r/articles/build/layout-guide/) based on {**bslib**}- [Bootstrap](https://getbootstrap.com/) is a free and open-source CSS framework designed for responsive, mobile-first front-end web development.- [Bootswatch](https://bootswatch.com/) is a collection of pre-built themes that can be easily applied to a Bootstrap project, simplifying the process of achieving a polished and professional look.:::::::::### Getting startedCreate a theme with `bslib::bs_theme()` then apply it to an app with thetheme argument of the page layout function:``` markdownfluidPage( theme = bslib::bs_theme(...))```If not specified, Shiny will use the classic Bootstrap v3 theme that ithas used basically since it was created. By default,`bslib::bs_theme()`, will use Bootstrap v5. Using Bootstrap v5 insteadof v3 will not cause problems if you only use built-in components. Thereis a possibility that it might cause problems if you’ve used customHTML, so you can force it to stay with v3 with `version = 3`.### Shiny themesThe easiest way to change the overall look of your app is to pick apremade “bootswatch” theme using the bootswatch argument to`bslib::bs_theme()`.::::::::::::::::::: my-code-collection::::: my-code-collection-header::: my-code-collection-icon:::::: {#exm-06-shiny-themes}: Title for code collection::::::::::::::::::::::: my-code-collection-container:::::::::::::: panel-tabset###### predefined theme:::::: my-r-code:::: my-r-code-header::: {#cnj-06-bslib-predefined-theme}: Shiny with a `bslib` predefined theme:::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| viewerHeight: 450#| components: [editor, viewer]ui <- fluidPage( theme = bslib::bs_theme(bootswatch = "darkly"), sidebarLayout( sidebarPanel( textInput("txt", "Text input:", "text here"), sliderInput("slider", "Slider input:", 1, 100, 30) ), mainPanel( h1(paste0("Theme: darkly")), h2("Header 2"), p("Some text") ) ))server <- function(input, output, session) {}shinyApp(ui, server)```:::::::::###### custom changes:::::: my-r-code:::: my-r-code-header::: {#cnj-bslib-with-custom-appearance}: Shiny with custom theme changes:::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| viewerHeight: 450#| components: [editor, viewer]#| error: trueui <- fluidPage( theme = bslib::bs_theme( bg = "#0b3d91", fg = "white", base_font = "Source Sans Pro" ), sidebarLayout( sidebarPanel( textInput("txt", "Text input:", "text here"), sliderInput("slider", "Slider input:", 1, 100, 30) ), mainPanel( h1(paste0("Theme: darkly")), h2("Header 2"), p("Some text") ) ))server <- function(input, output, session) {}shinyApp(ui, server)```:::::::::In my first trials I got always the error message "Text to be writtenmust be a length-one character vector". After googling the error messageI found out that the error message comes from {**htmltools**}:> This error occurs in htmltools::WSTextWriter (see> [here](https://rdrr.io/cran/htmltools/src/R/utils.R) and CTRL+F to> look for "writeImpl"). This function is in charge of properly writing> text to your display when you display a raw text in any UI function.> It raises the mentioned error when the `writeImpl()` function receives> more than one character string (aka element of a `character()`> vector). To correct this, make sure you never provide two character> strings to a Shiny UI function asking for only one character argument.> (From Eli Ker Ano, the first comment under the StackOverflow question> [Text to be written must be a length-one character> vector](https://stackoverflow.com/questions/58608109/text-to-be-written-must-be-a-length-one-character-vector)).I tried to follow this advice but to no avail. Even if I deleted alllines between `sidebarPanel()` and `mainPanel()` the error persisted.Could it be that {**bslib**} has changed in the meanwhile and requiresother layout commands? This means that it would not be fully compatiblewith Shiny anymore. That seems unlikely.::::: my-important::: my-important-header`=`instead of `<-` required for `theme = bslib::bs_theme()`:::::: my-important-containerAfter several hours I finally found the problem. The `<-` operator worksfine without additional parameters (allowed is only`bootswatch = "<theme name>"`) but with (additional) other arguments youhave to use the `=` operator.I copied the code snippet from the book and that was wrong! In the codechunk of `6.5.1 Shiny Themes` is the correct `=` operator used, but inthe code snippet below not.With `shinylive` in the Quarto document it works with the `<-` operatoras well!::::::::::::::::::::::::::::::::::::::::::::::::::::::::### Plot themesIf you’ve heavily customized the style of your app, you may want to alsocustomize your plots to match. Luckily, this is really easy thanks tothe {**thematic**} package which automatically themes {**ggplot2**},{**lattice**}, and base plots. Just call `thematic::thematic_shiny()` inyour server function. This will automatically determine all of thesettings from your app theme.:::::: my-r-code:::: my-r-code-header::: {#cnj-ID-text}: Numbered R Code Title:::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| viewerHeight: 400#| components: [editor, viewer]#| layout: verticallibrary(shiny)library(ggplot2)ui <- fluidPage( theme = bslib::bs_theme(bootswatch = "darkly"), titlePanel("A themed plot"), plotOutput("plot"),)server <- function(input, output, session) { thematic::thematic_shiny() output$plot <- renderPlot({ ggplot(mtcars, aes(wt, mpg)) + geom_point() + geom_smooth() }, res = 96)}shinyApp(ui, server)```:::::::::### Exercises {#sec-chap06-5_4_exercises}The suggested `bslib::bs_themer()` function worked in my case only with a native theme. Real-time theming worked but I had always to start with the fresh standard theme. I couldn't succeed to incorporate already those changes that I have already made to the theme.I learned two other live theme changing procedures that allows to start from an already specified theme:1.**`bslib::bs_theme_preview(theme)`** as recommended in the book. It is important that you fix those theme changes that you are satisfied with. For this you could use the function `theme = bslib::bs_theme(<here include the changes you already made>)`2.**`bslib::run_with_themer(shinyApp(ui, server))`** as explained in [Outstanding User Interfaces with Shiny](https://unleash-shiny.rinterface.com/beautify-with-bootstraplib#live-theming). Replace the `shinyApp(ui, server)`code line with `run_with_themer(shinyApp(ui, server))`and start the app. The `run_with_themer()`function allowsyou to work on the app and to modify the theme at the same time.::: {.callout-warning #wrn-06-no-curl-WASM-package}##### Compiled {**curl**} package for webR not availableI can't run this app in `shinyliv` because there is no `r glossary("WASM")` compatible compilation of the {**curl**} package available.```{block2, label='ex-06-bslib-theming-1', type='rmddanger'}An error has occurred! Downloading Google Font files requires either the curl package or `capabilities('libcurl')`.```Running `capabilities('libcurl')` in the console returns true but trying to install the packages in the [webR demo page](https://webr.r-wasm.org/latest/) with ````markdowninstall.packages('curl', repos = c('https://tidyverse.r-universe.dev', 'https://repo.r-wasm.org'))````returns ```{block2, label='ex-06-bslib-theming-2', type='rmdattention'}Warning message:In install.packages("curl", repos = c("https://tidyverse.r-universe.dev", : Requested package curl not found in webR binary repo.```In this special case the problem is connected with the download of Google font with `font_google(<font name>)`. A way around the problem is to download the Google fonts and integrate them into the operating system. In that case `shinylive` have access to these fonts and downloading fonts with `font_google(<font name>)` is not necessary anymore.:::For more information on the issue missing compiling WASM compatible R packages see @lem-compiling-packages-for-wasm.:::::{.my-resource}:::{.my-resource-header}:::::: {#lem-06-downloading-google-fonts}: How to download Google fonts and integrate them into macOS:::::::::::::{.my-resource-container}To download and install Google font see the [article by Junian Triajianto](https://www.junian.net/tech/macos-google-fonts/): How to Install ALL Google Fonts on macOS: A simple guide to install the entire Google Fonts library on macOS. He explains two different ways to accomplish the same: one procedure uses the terminal, the other not. Both are easy to understand and the written explanation is supported by two videos: One for the [terminal way](https://www.youtube.com/watch?v=brkktM8D-EY), the other [without terminal](https://www.youtube.com/watch?v=8aVw9pVg4wE).::::::::::::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-06-ex-06-ugliest-ui}: Real-time Shiny themes updates:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 550#| components: [editor, viewer]#| layout: vertical## file: app.R{{< include apps_06/06-5-2-ex-01-bs-theme-ugliest/app.R >}}```:::::::::## Under the hoodThere’s no magic behind all the input, output, and layout functions:they just generate HTML. (The magic is done by JavaScript, which isoutside the scope of the book.)You can see that HTML by executing UI functions directly in the console:``` markdownshiny::fluidPage( shiny::textInput("name", "What's your name?"))<div class="container-fluid"> <div class="form-group shiny-input-container"> <label for="name">What's your name?</label> <input id="name" type="text" class="form-control" value=""/> </div></div>```Note that this is the contents of the `<body>`tag;other parts of Shinytake care of generating the `<head>`. If you want to include additionalCSS or JS dependencies you’ll need to learn`htmltools::htmlDependency()`. Two good places to start arehttps://blog.r-hub.io/2020/08/25/js-r/#web-dependency-management andhttps://unleash-shiny.rinterface.com/htmltools-dependencies.html.It’s possible to add your own HTML to the ui. There are two differentways:- One way to do so is by including literal HTML with the`htmltools::HTML()`function. In the example below, the “[rawcharacterconstant](https://josiahparry.com/posts/2023-01-19-raw-strings-in-r.html)”,`r"()"`is used, to make it easier to include quotes in the string.You can even skip `fluidPage()`altogether and supply raw HTML forthe whole UI. See [Build your entire UI withHTML](https://shiny.posit.co/r/articles/build/html-ui/)for moredetails.- Alternatively, you can make use of the HTML helper that Shinyprovides via import of {**htmltools**} functions. There are regularfunctions for the most important elements like `h1()`and`p()`, andall others can be accessed via the other `tags`helper[^06-layouts-2]. Named arguments become attributes and unnamedarguments become children, so we can recreate the above HTML with these regular functions.[^06-layouts-2]: Because Shiny imports {**htmltools**} **it** istherefore not necessary to write `htmltools::h1()`,`htmltools::p()`etc. When `library(shiny)`is loaded just`h1() or`,`p()`is enough.:::::::::::::::: my-code-collection::::: my-code-collection-header::: my-code-collection-icon:::::: {#exm-06-html-ui}: Raw HTML for the UI:::::::::::::::::::: my-code-collection-container::::::::::: panel-tabset###### Raw HTML for UI:::::: my-r-code:::: my-r-code-header::: {#cnj-06-html-ui-raw}: Add HTML raw code snippets to the Shiny UI:::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| viewerHeight: 400#| components: [editor, viewer]ui <- shiny::fluidPage( htmltools::HTML(r"(<h1>This is a heading</h1><p class="my-class">This is some text!</p><ul><li>First bullet</li><li>Second bullet</li></ul>)"))server <- function(input, output, session) {}shinyApp(ui, server)```:::::::::###### HTML with functions:::::: my-r-code:::: my-r-code-header::: {#cnj-06-using-html-ui-helper}: Numbered R Code Title (Tidyverse):::::::::: my-r-code-container```{shinylive-r}#| standalone: true#| viewerHeight: 400#| components: [editor, viewer]ui <- fluidPage( h1("This is a heading"), p("This is some text", class = "my-class"), tags$ul( tags$li("First bullet"), tags$li("Second bullet") ))server <- function(input, output, session) {}shinyApp(ui, server)```::::::::::::::::::::::::::::::::::::::::::::::::One advantage of generating HTML with code is that you can interweave existing Shiny components into a custom structure. For example, the code below makes a paragraph of text containing two outputs, one which is bold:```markdowntags$p( "You made ", tags$b("$", textOutput("amount", inline = TRUE)), " in the last ", textOutput("days", inline = TRUE), " days " )```Note the use of `inline = TRUE`;the`textOutput()`default is to produce a complete paragraph.To learn more about using HTML, CSS, and JavaScript to make compelling user interfaces, I highly recommend David Granjon’s Outstanding User Interfaces with Shiny.:::::: my-resource:::: my-resource-header::: {#lem-06-html-css-js-for-shiny-ui}: HTML, CSS, and JavaScript to make compelling user interfaces:::::::::: my-resource-container- [Handle HTML dependencies with{**htmltools**}](https://unleash-shiny.rinterface.com/htmltools-dependencies.html)- [Web dependencymanagement](https://blog.r-hub.io/2020/08/25/js-r/#web-dependency-management)- [Build your entire UI withHTML](https://shiny.posit.co/r/articles/build/html-ui/)- [JavaScript for R](https://book.javascript-for-r.com/):::::::::