19  CSS

CSS in Shiny Applications

This chapter explores how to enhance the visual appeal of your Shiny app by incorporating custom CSS styles. It quickly covers the basics of CSS syntax, selectors, and properties, and demonstrates how to integrate CSS into Shiny apps. Key topics include:

  • Inline CSS

  • Internal CSS

  • External CSS files

Other topics include styling UI components, using CSS selectors, properties and values, and managing external CSS files.

Cascading Style Sheets (CSS) is a stylesheet language that serves as the backbone of web design, providing the tools to make Shiny applications functional and visually engaging. This chapter extends the Using custom CSS in your app article for including CSS in your Shiny app-package. We’ll also cover a brief introduction to CSS fundamentals and illustrate how it can enhance the aesthetics of a Shiny app.

I’ve created the shinypak R package In an effort to make each section accessible and easy to follow:

Install shinypak using pak (or remotes):

# install.packages('pak')
pak::pak('mjfrigaard/shinypak')

Review the chapters in each section:

library(shinypak)
list_apps(regex = '^18')
## # A tibble: 4 × 2
##   branch            last_updated       
##   <chr>             <dttm>             
## 1 18_html           2024-06-28 08:15:15
## 2 18.1_css-inline   2024-05-20 05:23:27
## 3 18.2_css-internal 2024-03-18 11:54:15
## 4 18.3_css-file     2024-03-18 14:20:11

Launch an app with launch()

launch(app = "18.1_css-inline")

Download an app with get_app()

get_app(app = "18.1_css-inline")

What is CSS?

CSS is used to define the presentation of an HTML or XML web document. CSS describes how elements should be rendered on screen, paper, speech, or other media, which is particularly relevant to Shiny apps, as Shiny utilizes HTML to render UI elements.

Originally, HTML was designed solely to describe web content, not to format pages. However, the introduction of formatting tags and attributes in HTML 3.2 complicated web development, making the creation of large websites costly and time-consuming. To address this, the W3C introduced CSS to separate style from HTML content.

CSS syntax is typically a series of statements comprised of selectors and declarations. The selectors tell the HTML what to style and include names similar to HTML tags (h1, h2) but can also include more complex classes (i.e., custom-sidebar). The declaration includes properties and values: properties are style attributes like color, text-align, or margin, and the values are the settings for each property.

With CSS, you can control your Shiny app’s layout, color, font, and overall visual appearance, making it an indispensable tool for developers looking to customize their applications.

Why use CSS in app-packages?

As we’ve seen, app-packages essentially bundle various application components, including the code for the UI elements, server logic, and any data. Adding CSS to this list of components serves several purposes:

  • Customization: CSS allows you to go beyond the default styles provided by Shiny, offering the freedom to personalize your app’s look and feel.

  • Consistency: Using CSS, you can maintain visual consistency across different parts of your app or multiple apps within the same package.

  • Efficiency: CSS can help reduce the complexity of your app’s UI code by separating content (HTML) from presentation (CSS). This separation makes your code cleaner and easier to maintain.

Incorporating CSS

There are three ways to include CSS in your Shiny apps:

  1. Inline: inline CSS can be used directly within the HTML elements using the style attribute and is suitable for quick, small-scale styling.

  2. Internal: This method uses the tags$style() function within the app’s UI definition and is ideal for app-specific styles that won’t be reused.

  3. External Files: This approach references external CSS files stored within the R package. A CSS file is best for extensive styling that applies to multiple apps or for sharing styles across different projects.

We’ll demonstrate each of these methods in the application launched with the launch_app() function.

19.1 Inline CSS

Lets start at the top of the UI and work our way down. The shiny package exports a collection of HTML tags in the shiny::tags list:

Launch app with the shinypak package:

launch('18.1_css-inline')
show/hide HTML tags
names(shiny::tags)
##   [1] "a"                   "abbr"               
##   [3] "address"             "animate"            
##   [5] "animateMotion"       "animateTransform"   
##   [7] "area"                "article"            
##   [9] "aside"               "audio"              
##  [11] "b"                   "base"               
##  [13] "bdi"                 "bdo"                
##  [15] "blockquote"          "body"               
##  [17] "br"                  "button"             
##  [19] "canvas"              "caption"            
##  [21] "circle"              "cite"               
##  [23] "clipPath"            "code"               
##  [25] "col"                 "colgroup"           
##  [27] "color-profile"       "command"            
##  [29] "data"                "datalist"           
##  [31] "dd"                  "defs"               
##  [33] "del"                 "desc"               
##  [35] "details"             "dfn"                
##  [37] "dialog"              "discard"            
##  [39] "div"                 "dl"                 
##  [41] "dt"                  "ellipse"            
##  [43] "em"                  "embed"              
##  [45] "eventsource"         "feBlend"            
##  [47] "feColorMatrix"       "feComponentTransfer"
##  [49] "feComposite"         "feConvolveMatrix"   
##  [51] "feDiffuseLighting"   "feDisplacementMap"  
##  [53] "feDistantLight"      "feDropShadow"       
##  [55] "feFlood"             "feFuncA"            
##  [57] "feFuncB"             "feFuncG"            
##  [59] "feFuncR"             "feGaussianBlur"     
##  [61] "feImage"             "feMerge"            
##  [63] "feMergeNode"         "feMorphology"       
##  [65] "feOffset"            "fePointLight"       
##  [67] "feSpecularLighting"  "feSpotLight"        
##  [69] "feTile"              "feTurbulence"       
##  [71] "fieldset"            "figcaption"         
##  [73] "figure"              "filter"             
##  [75] "footer"              "foreignObject"      
##  [77] "form"                "g"                  
##  [79] "h1"                  "h2"                 
##  [81] "h3"                  "h4"                 
##  [83] "h5"                  "h6"                 
##  [85] "hatch"               "hatchpath"          
##  [87] "head"                "header"             
##  [89] "hgroup"              "hr"                 
##  [91] "html"                "i"                  
##  [93] "iframe"              "image"              
##  [95] "img"                 "input"              
##  [97] "ins"                 "kbd"                
##  [99] "keygen"              "label"              
## [101] "legend"              "li"                 
## [103] "line"                "linearGradient"     
## [105] "link"                "main"               
## [107] "map"                 "mark"               
## [109] "marker"              "mask"               
## [111] "menu"                "meta"               
## [113] "metadata"            "meter"              
## [115] "mpath"               "nav"                
## [117] "noscript"            "object"             
## [119] "ol"                  "optgroup"           
## [121] "option"              "output"             
## [123] "p"                   "param"              
## [125] "path"                "pattern"            
## [127] "picture"             "polygon"            
## [129] "polyline"            "pre"                
## [131] "progress"            "q"                  
## [133] "radialGradient"      "rb"                 
## [135] "rect"                "rp"                 
## [137] "rt"                  "rtc"                
## [139] "ruby"                "s"                  
## [141] "samp"                "script"             
## [143] "section"             "select"             
## [145] "set"                 "slot"               
## [147] "small"               "solidcolor"         
## [149] "source"              "span"               
## [151] "stop"                "strong"             
## [153] "style"               "sub"                
## [155] "summary"             "sup"                
## [157] "svg"                 "switch"             
## [159] "symbol"              "table"              
## [161] "tbody"               "td"                 
## [163] "template"            "text"               
## [165] "textarea"            "textPath"           
## [167] "tfoot"               "th"                 
## [169] "thead"               "time"               
## [171] "title"               "tr"                 
## [173] "track"               "tspan"              
## [175] "u"                   "ul"                 
## [177] "use"                 "var"                
## [179] "video"               "view"               
## [181] "wbr"

All of the objects in the shiny::tags list are functions that generate HTML:

shiny::tags$div()
<div></div>

We’ll use tags$h2() inside our app’s titlePanel() to add the following CSS directly to the style argument:

tags$h2(
  style = "color: #02577A; text-align: center;",
  "Welcome to moviesApp!")
1
h2 selector defined with shiny::tags
2
CSS styling with style argument
3
Text to be styled

In this example, the h2 text elements within the call to tags$h2() will have their color set to #02577A and be center-aligned. If we pass this function to the Console, we see the following HTML:

<h2 style="color: #02577A; text-align: center;"Welcome to moviesApp!</h2>

The CSS statement above consists of a single selector and declaration with two properties and accompanying values:1

CSS Syntax

As we can see, the <h2> HTML tag includes the style argument with the declaration properties and values. The updated text in our UI title panel is below:

Rendered inline CSS for tags$h2()

19.2 Internal CSS

If we’d like to include more complicated styling, we can add this syntax internally using tags$style(). Anything you define within tags$style() will be applied as CSS styling rules to the HTML elements of your app.

Launch app with the shinypak package:

launch('18.2_css-internal')

We’ll define a custom style for a few elements in a new custom-sidebar class selector. Class selectors are used to apply the same styling rules to any HTML element that includes the specified class name in its class attribute.

We’ll define and set the background color to a light blue, gray text, and add a comfortable amount of space around the content in custom-sidebar below:

  • .custom-sidebar: The period (.) before the name indicates that it is a class selector in CSS.
tags$style("
    .custom-sidebar
  ")
  • { ... }: Inside the style block, we define a CSS rule for elements that have a class attribute of custom-sidebar.
tags$style("
    .custom-sidebar {
      ...
    }")
  • background-color: #ecfafd;: This CSS property sets the background color of the .custom-sidebar elements using a hexadecimal code (#ecfafd), which is a light blue in this case.2
tags$style("
    .custom-sidebar {
      background-color: #ecfafd;
    }")
  • color: #696969;: This property sets the color of the text inside the .custom-sidebar elements to a shade of gray.
tags$style("
    .custom-sidebar {
      background-color: #ecfafd;
      color: #696969;
    }")
  • padding: 10px;: Padding is the breathing room between the content of the .custom-sidebar element and its border (10 pixels on all sides).
tags$style("
    .custom-sidebar {
      background-color: #ecfafd;
      color: #696969;
      padding: 10px;
    }")

When we want to use the .custom-sidebar styling, we can specify it’s name in the class argument of the UI function (like sidebarPanel() below):

sidebarLayout(
    sidebarPanel(
        class = "custom-sidebar",
        mod_var_input_ui("vars")
      ),
      mainPanel(
        mod_scatter_display_ui("plot")
    )
)
1
Use the CSS styling from tags$style()

After loading our package and launching our app, we see the updated sidebar in the UI:

Internal CSS in the UI with tags$style()

19.3 External CSS files

In app-packages, it’s generally better to use external CSS files for reproducibility and ease of maintenance.

19.3.1 Font Awesome

loaded manually..

19.3.2 Local CSS files

We’ll create a file named my_styles.css to hold all the custom styles we want to apply to our app and store in the inst/www folder (along with any images and external resources).

Launch app with the shinypak package:

launch('18.3_css-file')
moviesApp/
  inst/
    └── www
        ├── bootstrap.png
        ├── my_styles.css
        └── shiny.png

The contents of my_styles.css are described below:

19.3.2.2 Text

If we have HTML elements that we’d like to style with more than one class selector, we can provide multiple items to the class argument. For example, suppose custom-text has properties and values we’d like to use in future class selectors.

.custom-text {
    font-size: 18px;
    word-spacing: 1.5px;
    font-weight: bold;
}

19.3.2.4 Main

The main panel will also have custom colors, but we can provide additional text properties (along with custom-text).

.custom-main {
    background-color: #00ddff;
    color: #ffffff;
    font-style: italic;
    padding: 15px;
}
mainPanel(
    class = c("custom-main", 
              "custom-text"),
    mod_scatter_display_ui("plot")
)

CSS external file

19.4 Referencing CSS files

In your Shiny application’s UI definition, reference the CSS file using the addResourcePath() and tags$link() functions.4

In movies_ui(), this looks like:

movies_ui <- function(bslib = FALSE) {
addResourcePath(
    prefix = "www",
    directoryPath = system.file("www", package = "moviesApp")
    # The rest of the UI app function goes here
tagList(
    fluidPage(
      tags$link(
        rel = "stylesheet", 
        type = "text/css", 
        href = "www/my_styles.css"
        )
        # The rest of the UI app function goes here

Since the app is inside an R package, the system.file() function will build a path to the installed version of your package when the application is run/deployed.

Recap

Integrating CSS into your app-package opens a world of possibilities for customization and enhancement. Understanding the basics of CSS gives you the ability to significantly improve the visual appeal and user experience of your Shiny app. Remember, the goal is not just to make your apps work well but also to make them look great and provide an engaging experience for the end-user.

Tips when using CSS in your Shiny apps
  • Keep it organized: Whether you’re using inline, internal, or external CSS, keeping your styles organized is crucial for maintenance and future updates.

  • Use meaningful names: When naming CSS classes or IDs, use names that reflect the purpose or content of the elements they style.

  • Optimize for readability: Ensure your CSS is easily read using comments, consistent indentation, and spacing.

  • Test across browsers: Different web browsers can render CSS differently. Test your Shiny app across multiple browsers to ensure a consistent appearance.


  1. I’ve also included the Shiny icon using img(), addResourcePath(), and system.file() we covered back in Section 9.1.3.↩︎

  2. Hex codes are a way of specifying colors in web design and can represent millions of colors by combining different levels of red, green, and blue.↩︎

  3. margin: 10px 10px; if two values are listed for margin, this translates to ‘top/bottom margins are 10px, right/left margins are 10px.’↩︎

  4. We covered using the inst/ folder with system.file() in the Section 9.1 chapter.↩︎