8 Beautify with {fresh}

As shown in the previous chapter, Sass is a powerful tool to customize apps in minutes, in addition to drastically improve code quality, thereby empowering long term maintenance. Some CSS knowledge are required and it may not be that easy to find the corresponding variables to a specific purpose. For instance, would you be able to guess what Bootstrap 4 variables are required to customize the bs4Dash template?

In this chapter, as well as in chapter 9, we show higher level tools to customize Bootstrap (3 or 4 and more) based templates. Let’s start with the first one: fresh.

{fresh} is an R package developed by the dreamRs team, also authors of shinyWidget and esquisse. All dreamRs projects are clearly a great source of inspiration to design outstanding Shiny apps, to such an extent that we highly recommend the reader to explore more about their work!

8.1 {fresh}, the big picture

fresh is built on top of sass and what you see in Figures 8.5 and 8.6 may be done in few minutes! To design a new theme, the main function is create_theme():

create_theme(
  ...,
  theme = c("default", "cerulean", ...),
  output_file = NULL,
  include_assets = FALSE
)

theme allows to import a Bootswatch CSS theme, you may already know if you ever used shinythemes. output_file controls the output. If provided, create_theme() creates a CSS file at the specified location. Otherwise, it returns a string value containing the compiles CSS. It is useful in a package context where you don’t necessarily want to generate a new theme each time. is where we pass a list of CSS variables, through bs4Dash_* (specific to bs4Dash), adminlte_* (for shinydashboard) or bs_vars_* (for classic shiny apps) functions, which we describe below.

In general, you’ll have to call use_theme() inside the app UI to load the newly generated theme.

Note that bs4Dash and shinydashboardPlus expose a freshTheme parameter, which does handle the provided theme on the fly.

8.1.1 Customize {bs4Dash}

In what follows, we explain how to set a blue ocean theme in only few minutes.

8.1.1.1 Statuses and colors

In chapter 7.5.1, we showed how to change bs4Dash colors, with few lines of Sass code. Guess what: fresh makes it even easier! The bs4dash_status() and bs4Dash_color() functions allows to overwrite all the default statuses and colors:

bs4dash_status(
  primary = NULL,
  secondary = NULL,
  success = NULL,
  info = NULL,
  warning = NULL,
  danger = NULL,
  light = NULL,
  dark = NULL
)

bs4dash_color(
  blue = NULL,
  lightblue = NULL,
  navy = NULL,
  cyan = NULL,
  teal = NULL,
  olive = NULL,
  green = NULL,
  lime = NULL,
  orange = NULL,
  yellow = NULL,
  fuchsia = NULL,
  purple = NULL,
  maroon = NULL,
  red = NULL,
  black = NULL,
  gray_x_light = NULL,
  gray_600 = NULL,
  gray_800 = NULL,
  gray_900 = NULL,
  white = NULL
)

By default, primary is blue but could become green with just one line of code. Do you recall about section 7.5.1, where we customized bs4Dash colors with Sass? Let’s try again with fresh. We first create the new theme, passing it the new colors and inject it inside the previously mentioned freshTheme parameter:

library(shiny)
library(bs4Dash)
library(fresh)

custom_colors_theme <- create_theme(
  bs4dash_color(
    lightblue = "#136377",
    olive = "#d8bc66",
    lime = "#fcec0c",
    orange = "#978d01",
    maroon = "#58482c",
    gray_x_light = "#d1c5c0"
  )
)


ui <- dashboardPage(
  freshTheme = custom_colors_theme,
  dashboardHeader(title = "Custom colors"),
  dashboardSidebar(),
  dashboardBody(
    # Boxes need to be put in a row (or column)
    fluidRow(
      box(
        solidHeader = TRUE,
        plotOutput("plot1", height = 250), 
        status = "olive"
      ),
      box(
        solidHeader = TRUE,
        status = "lightblue",
        title = "Controls",
        sliderInput(
          "slider", 
          "Number of observations:", 
          1, 
          100, 
          50
        )
      )
    )
  )
)

server <- function(input, output) {
  set.seed(122)
  histdata <- rnorm(500)

  output$plot1 <- renderPlot({
    data <- histdata[seq_len(input$slider)]
    hist(data)
  })
}

shinyApp(ui, server)

Note that complex variable names like gray-x-light become gray_x_light!

Compared to the approach described in 7.5.1, there a few advantages:

  • We don’t have to specify the AdminLTE.scss location, as fresh hosts it here.
  • We don’t have to look for all colors names as they are described along the bs4dash_color function definition. We even have a description of the default values (as well as statuses in bold), depicted on Figure 8.1 below.
Default colors provided by {bs4Dash}

FIGURE 8.1: Default colors provided by {bs4Dash}

Corporate users will appreciate to set up a custom internal theme in minutes.

8.1.1.2 General Layout

bs4Dash_layout exposes variables to control the sidebar width when expanded or collapsed, the sidebar padding, the controlbar width, the main background color and the main content padding. For the blue ocean theme, we change the body background color passing a new value to the main_bg variable, as below (Figure 8.2):

layout_vars <- bs4dash_layout(main_bg = "#006994")
ocean_theme <- create_theme(layout_vars)
Customized body background color

FIGURE 8.2: Customized body background color

We acknowledge the result is not yet consistent but it gives a small glimpse about the package capabilities.

8.1.1.4 Text color

The card text element would not properly be styled without setting the white color to the global ocean blue theme color, as they would render dark, which is a bad contrast option. Hence, we change the white and gray_900 colors with bs4dash_color().

inverted_colors <- bs4dash_color(
  gray_900 = "#fff", 
  white = "#005475"
)
ocean_theme <- create_theme(
  layout_vars, 
  navbar_vars, 
  inverted_colors
)
## Warning: Using custom variables with bs4dash SCSS files

8.1.1.5 Color contrast

bs4dash_yiq() fine tunes the contrast between a given color and its background. It relies on the Bootstrap 4 color-yiq function, whose code may be found below. Interested readers will have a look at the following article, that explain better where this calculation is derived from.

@function color-yiq($color, $dark: $yiq-text-dark, 
                    $light: $yiq-text-light) {
  $r: red($color);
  $g: green($color);
  $b: blue($color);

  $yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000;

  @if ($yiq >= $yiq-contrasted-threshold) {
    @return $dark;
  } @else {
    @return $light;
  }
}

The function has three major steps:

  • Given a color, we extract its three components in the rgb space.
  • The yiq value is computed from these contributions, according to the above formula.
  • The threshold determines the final color value. If yiq is higher than the threshold, the color is black, and white otherwise.

As an example, let’s apply this to the default AdminLTE3 primary color #0073b7, with a threshold value of 150. We included an extra parameters to the color-yiq function, that represents the threshold value (defaulting to 150).

We utilize knowledge from Chapter 7, particularly, the sass_layer() function to separate functions/mixins from rules and defaults:

color_yiq <- "
  @function color-yiq($color, $threshold: 150, 
  $dark: $yiq-text-dark, $light: $yiq-text-light) {
    $r: red($color);
    $g: green($color);
    $b: blue($color);

    $yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000;

    @if ($yiq >= $threshold) {
      @return $dark;
    } @else {
      @return $light;
    }
  }
"

background <- "
  $bg: #0073b7 !default;
  $yiq-text-dark: #111 !default;
  $yiq-text-light: #fff !default;
"
test_contrast <- ".test { background: color-yiq($bg); }"
sass(
  sass_layer(
    default = background,
    declarations = color_yiq,
    rules = test_contrast
  )
)
## Warning in sass_layer(default = background, declarations = color_yiq, rules =
## test_contrast): partial argument match of 'default' to 'defaults'

Wait a moment: bs4dash_yiq() does not expose any color parameter! Indeed, the color is already implicitly applied by the AdminLTE Sass code.

As a last example, we design an app with a slider input controlling the contrast threshold and a radio input color picker to set the box current status color. We dynamically call bs4dash_yiq() and create_theme, with an initial threshold of 150 (Bootstrap default) and a dark and light values set to primary and white, respectively. The initial status is primary (blue), and as shown above, we expect the yiq value to be lower than the threshold since the blue color has a relatively light weight for the calculation, thereby fixing the background text color to white. As an exercise, you may try to reduce the threshold until the text becomes black. What is the threshold value. Then, replace the card status from primary to warning. What happens? Why? As yellow is made of green and red, which have the highest contributions in the yiq calculation, this result is not surprising!

statuses <- c(
  "primary" = "#0073b7",
  "secondary" = "#6c757d",
  "success" = "#28a745",
  "info" = "#17a2b8",
  "warning" = "#ffc107",
  "danger" = "#dc3545"
)

ui <- dashboardPage(
  dashboardHeader(title = "Card with custom contrast"),
  dashboardSidebar(),
  dashboardBody(
    # Boxes need to be put in a row (or column)
    uiOutput("sass"),
    fluidRow(
      sliderInput(
          "threshold", 
          "Threshold", 
          min = 0, 
          max = 255, 
          value = 150
        ),
      shinyWidgets::colorSelectorInput(
        inputId = "status",
        label = "Card Status color",
        choices = statuses
      )
    ),
    fluidRow(
      box(
        id = "mybox",
        solidHeader = TRUE,
        title = "You can see me!",
        status = "primary"
      )
    )
  )
)

server <- function(input, output, session) {
  
  observeEvent(input$status, {
    status_name <- names(which(statuses == input$status))
    updateBox(
      id = "mybox", 
      action = "update", 
      options = list(status = status_name)
    )
  })
  
  output$sass <- renderUI({
    use_theme(css())
  })
  css <- reactive({
    # Recover the hex code since bs4dash_yiq 
    # does not accept names
    
    create_theme(
      bs4dash_yiq(
        contrasted_threshold = input$threshold, 
        text_dark = "#111", 
        text_light ="#fff"
      )
    )
  })
}
shinyApp(ui, server)

The output is shown Figure 8.3

Color contrast function in action

FIGURE 8.3: Color contrast function in action

8.1.2 Customize {shinydashboard}

Similarly, fresh supports shinydashboard powered apps. In few lines of code, you may definitely provide a cyberpunk look and feel to your favorite shiny dashboard (Figure 8.6). shinydashboardPlus (v2) has a plug and play support for fresh where the theme has to be passed to the dashboardPage freshTheme parameter (it would also seamlessly work with shinydashboard). We start by creating the theme with adminlte_colors(), adminlte_sidebar() and adminlte_global():

cyberpunk_theme <- create_theme(
  adminlte_color(
    green = "#3fff2d",
    blue = "#2635ff",
    red = " #ff2b2b",
    yellow = "#feff6e",
    fuchsia = "#ff5bf8",
    navy = "#374c92",
    purple = "#615cbf",
    maroon = "#b659c9",
    light_blue = "#5691cc"
  ),
  adminlte_sidebar(
    dark_bg = "#D8DEE9",
    dark_hover_bg = "#81A1C1",
    dark_color = "#2E3440"
  ),
  adminlte_global(
    content_bg = "#aaaaaa"
  )
)

The demonstration may be run with, the result being shown on Figure 8.6:

customize_shinydashboard(cyberpunk_theme)
Cyberpunk shinydashboard

FIGURE 8.6: Cyberpunk shinydashboard