Theming a Shiny app with NDPalette

NDPalette carries the same Notre Dame colors through every layer of an interactive report, so a Shiny app’s page chrome, its prose, and its figures all read as one brand palette. This vignette builds a small, complete app, explains the three pieces that do the theming, and closes with a showcase of every color the package provides, with notes on what each is for. It is an independent project, not affiliated with or endorsed by the University.

A Shiny app themed with NDPalette rests on three pieces:

  1. a bslib theme whose Bootstrap colors are pulled from the palette with nd_color(), so buttons, links, and headers are brand-colored;
  2. the scale_*_nd() scales, so every figure is painted from the same palette; and
  3. a non-white background from nd_tints or nd_informal_tints.

A complete example app

The app below is shipped with the package. Run it with:

shiny::runApp(system.file("examples", "nd-shiny-app", package = "NDPalette"))

Its full source (inst/examples/nd-shiny-app/app.R), shown straight from the installed file so it never drifts from what actually runs:

# A Shiny palette explorer themed with NDPalette.
#
# It colors the page chrome from the Notre Dame palette with a bslib theme,
# lets you switch among three page backgrounds (plain white, the informal
# soft white, and the informal soft yellow), paints the figure with the
# scale_*_nd() scales, and shows the exact theme code with a copy button and
# a show/hide toggle. A download button saves the current palette as a CSV.
#
# Run it with:
#   shiny::runApp(system.file("examples", "nd-shiny-app", package = "NDPalette"))

library(shiny)
library(ggplot2)
library(NDPalette)

# Three page backgrounds to choose from: plain white, and two soft
# informal tints (which are NOT Notre Dame brand colors -- non-white
# alternatives to a plain white page).
bg_choices <- c(
  "White"       = "#ffffff",
  "Soft white"  = unname(nd_informal_tints[["soft_white"]]),
  "Soft yellow" = unname(nd_informal_tints[["soft_yellow_light"]])
)

# A bslib theme with the chosen background; text and accents stay brand
# colors, so only the page background changes between choices.
nd_theme <- function(bg) {
  bslib::bs_theme(
    version   = 5,
    bg        = bg,
    fg        = nd_color("navy"),
    primary   = nd_color("navy"),
    secondary = nd_color("bright_gold")
  )
}

# The theme code as text, for the copy-the-code panel.
theme_code <- function(bg_name, bg) {
  paste(
    "bslib::bs_theme(",
    "  version   = 5,",
    sprintf('  bg        = "%s",   # %s', bg, bg_name),
    '  fg        = nd_color("navy"),',
    '  primary   = nd_color("navy"),',
    '  secondary = nd_color("bright_gold")',
    ")",
    sep = "\n"
  )
}

ui <- bslib::page_sidebar(
  theme = nd_theme(bg_choices[["Soft white"]]),
  title = "NDPalette — a Notre Dame palette explorer",
  sidebar = bslib::sidebar(
    radioButtons("bg", "Page background", names(bg_choices),
                 selected = "Soft white"),
    selectInput("palette", "Palette",
                c("Notre Dame (nd)"              = "nd",
                  "Colorblind-friendly (nd_cvd)" = "nd_cvd",
                  "Former colors (former)"       = "former")),
    sliderInput("n", "Number of groups", min = 2, max = 10, value = 5),
    checkboxInput("reverse", "Reverse color order", FALSE),
    checkboxInput("show_code", "Show theme code", FALSE),    # the hide option
    downloadButton("dl", "Download palette (CSV)", class = "btn-sm")
  ),
  bslib::card(
    bslib::card_header("Swatches"),
    plotOutput("swatch", height = "110px")
  ),
  # The theme code, shown only when "Show theme code" is checked, with a
  # button that copies it to the clipboard.
  conditionalPanel(
    condition = "input.show_code",
    bslib::card(
      bslib::card_header(
        "Theme code",
        tags$button(
          "Copy", class = "btn btn-sm btn-primary float-end",
          onclick = paste0(
            "navigator.clipboard.writeText(",
            "document.getElementById('themecode').innerText)")
        )
      ),
      verbatimTextOutput("themecode")
    )
  ),
  bslib::card(
    bslib::card_header("A grouped figure painted with scale_color_nd()"),
    plotOutput("plot", height = "340px")
  ),
  bslib::card(
    bslib::card_header("Color reference (nd_colors)"),
    tableOutput("table")
  )
)

server <- function(input, output, session) {
  # Switch the live theme when the background choice changes.
  observeEvent(input$bg, {
    session$setCurrentTheme(nd_theme(bg_choices[[input$bg]]))
  })

  cols <- reactive(
    nd_palette(input$n, palette = input$palette, reverse = input$reverse)
  )

  output$swatch <- renderPlot(show_palette(cols()))

  output$plot <- renderPlot({
    set.seed(113)
    groups <- factor(LETTERS[seq_len(input$n)])
    df <- data.frame(x = rnorm(input$n * 40), y = rnorm(input$n * 40),
                     group = rep(groups, each = 40))
    ggplot(df, aes(x, y, color = group)) +
      geom_point(size = 2, alpha = 0.85) +
      scale_color_nd(palette = input$palette, reverse = input$reverse) +
      theme_minimal(base_size = 13) +
      labs(x = NULL, y = NULL, color = NULL)
  })

  output$themecode <- renderText(theme_code(input$bg, bg_choices[[input$bg]]))

  output$table <- renderTable(
    nd_colors[, c("name", "key", "hex", "role", "description")]
  )

  output$dl <- downloadHandler(
    filename = function() sprintf("nd_palette_%s_%d.csv", input$palette, input$n),
    content  = function(file) {
      utils::write.csv(data.frame(position = seq_along(cols()), hex = cols()),
                       file, row.names = FALSE)
    }
  )
}

shinyApp(ui, server)

Beyond the theming, the app lets you switch among three page backgrounds (plain white, the informal soft white, and the informal soft yellow) live with session$setCurrentTheme(), shows the exact bs_theme() code in a panel you can show or hide with a checkbox, with a Copy button that puts it on the clipboard, and a Download palette (CSV) button. Three small pieces do that work:

# 1. The three backgrounds (white, plus two soft informal tints):
bg_choices <- c("White"       = "#ffffff",
                "Soft white"  = nd_informal_tints[["soft_white"]],
                "Soft yellow" = nd_informal_tints[["soft_yellow_light"]])

# 2. Re-theme live when the choice changes:
observeEvent(input$bg, session$setCurrentTheme(nd_theme(bg_choices[[input$bg]])))

# 3. A copy-to-clipboard button for the shown code (plain HTML + JS):
tags$button("Copy", class = "btn btn-sm btn-primary",
            onclick = "navigator.clipboard.writeText(
                         document.getElementById('themecode').innerText)")

The three page backgrounds, side by side — plain white and two soft informal tints, all carrying navy text. (For an even lighter touch, the faint_white and faint_yellow tints sit just off pure white.)

How the theming works

A brand-colored bslib theme

bslib::bs_theme() accepts colors for the Bootstrap roles. Pulling them from nd_color() ties the page chrome to the palette: navy foreground and primary, bright gold secondary, on a soft background.

nd_theme <- bslib::bs_theme(
  version   = 5,
  bg        = nd_informal_tints[["soft_yellow"]],  # soft, non-white page
  fg        = nd_color("navy"),
  primary   = nd_color("navy"),
  secondary = nd_color("bright_gold")
)

For a more reserved page, use one of the official Warm Whites instead of the informal soft yellow, for example bg = nd_tints[["warm_white"]].

Brand-colored figures

Inside renderPlot(), the scale_*_nd() scales paint the figure from the same palette the page uses. They take a palette argument, so the app’s palette selector can switch a plot between the default Notre Dame colors and the colorblind-friendly ordering:

set.seed(113)
df <- data.frame(x = rnorm(120), y = rnorm(120),
                 group = factor(rep(LETTERS[1:3], length.out = 120)))
ggplot(df, aes(x, y, color = group)) +
  geom_point(size = 2, alpha = 0.85) +
  scale_color_nd(palette = "nd_cvd") +
  labs(color = NULL)

Reusing the R Markdown stylesheet

For a Shiny app that leans on plain HTML rather than Bootstrap components, the package’s R Markdown stylesheet can be included directly, which also sets the soft Warm White page background:

ui <- fluidPage(
  shiny::includeCSS(nd_css_path()),
  titlePanel("My Notre Dame app")
  # ...
)

All the colors, with details

The app’s palette selector exposes the three palettes; the catalog behind them is shown here in full, with a note on what each group is for.

The default Notre Dame palette ("nd")

Thirteen data colors: six leading brand colors that read clearly on white, then seven former Notre Dame colors to extend the set past six. This is the palette scale_*_nd() uses by default.

pal   <- nd_palette()
named <- nd_colors[match(pal, nd_colors$hex), ]
show_palette(pal, labels = named$name)

The colorblind-friendly ordering ("nd_cvd")

The same Notre Dame colors, reordered so the first several stay distinguishable under simulated deuteranopia, protanopia, and tritanopia. Ten anchors, leading with the CVD-safe ND Blue and Bright Gold. Reach for it with scale_color_nd(palette = "nd_cvd") when colorblind safety matters more than leading with the brand colors.

show_palette(nd_palette(palette = "nd_cvd"))

The former Notre Dame colors ("former")

Seven historical brand colors, no longer in the current brand guide, kept to widen the categorical palette. They are approximate, legacy colors and should not be read as current Notre Dame colors.

show_palette(nd_palette(palette = "former"))

Near-white brand tints and informal backgrounds

nd_tints holds the four official near-white Notre Dame tints, for page or panel backgrounds, fills, and the light end of a sequential ramp. They are too light to read as data colors and are never returned by nd_palette().

nd_tints
#>       warm_white light_warm_white  medium_sky_blue   light_sky_blue 
#>        "#efe9d9"        "#f8f4ec"        "#e1e8f2"        "#edf2f9"

nd_informal_tints holds four warm backgrounds, from a light warm white to a warmer soft yellow. These are not Notre Dame brand colors; they are alternatives to a white background (the example app offers white, soft white, and soft yellow).

nd_informal_tints
#>       faint_white        soft_white      faint_yellow soft_yellow_light 
#>         "#fdfcfa"         "#faf7f1"         "#fefdf3"         "#fdf9e6" 
#>       soft_yellow  soft_yellow_warm 
#>         "#faf3d7"         "#f6edc6"

The full color catalog

nd_colors is the complete reference: every color the package knows about, labeled with its brand (University, Athletics, or none for the informal backgrounds) and its role. nd_color() pulls any of them out by name or by role.

nd_colors
#>                   name                key     hex      brand      role
#> 1              ND Blue               navy #0c2340 university   primary
#> 2     ND Metallic Gold      metallic_gold #ae9142 university   primary
#> 3          Medium Blue        medium_blue #143865 university secondary
#> 4          Bright Blue        bright_blue #1c4f8f university secondary
#> 5            Dark Gold          dark_gold #8c7535 university secondary
#> 6          Bright Gold        bright_gold #d39f10 university secondary
#> 7                Green              green #0a843d university secondary
#> 8          Light Green        light_green #b3dac5 university secondary
#> 9        Dark Sky Blue      dark_sky_blue #c1cddd university secondary
#> 10          Warm White         warm_white #efe9d9 university      tint
#> 11    Light Warm White   light_warm_white #f8f4ec university      tint
#> 12     Medium Sky Blue    medium_sky_blue #e1e8f2 university      tint
#> 13      Light Sky Blue     light_sky_blue #edf2f9 university      tint
#> 14                Teal               teal #36deb8 university    former
#> 15              Maroon             maroon #4c1c2a university    former
#> 16              Purple             purple #342551 university    former
#> 17         Light Olive        light_olive #a8a600 university    former
#> 18          Dark Olive         dark_olive #456300 university    former
#> 19         Pale Yellow        pale_yellow #e3e361 university    former
#> 20               Black              black #1e1a13 university    former
#> 21         Faint White        faint_white #fdfcfa       none  informal
#> 22          Soft White         soft_white #faf7f1       none  informal
#> 23        Faint Yellow       faint_yellow #fefdf3       none  informal
#> 24 Soft Yellow (light)  soft_yellow_light #fdf9e6       none  informal
#> 25         Soft Yellow        soft_yellow #faf3d7       none  informal
#> 26  Soft Yellow (warm)   soft_yellow_warm #f6edc6       none  informal
#> 27     Notre Dame Blue     athletics_blue #0c2340  athletics   primary
#> 28  Standard Dome Gold          dome_gold #c99700  athletics   primary
#> 29  Metallic Dome Gold metallic_dome_gold    <NA>  athletics   primary
#> 30         Irish Green        irish_green #00843d  athletics secondary
#> 31               White    athletics_white #ffffff  athletics   neutral
#>    white_safe   pms
#> 1        TRUE   289
#> 2        TRUE 10127
#> 3        TRUE  2154
#> 4        TRUE  2945
#> 5        TRUE  4495
#> 6        TRUE   117
#> 7        TRUE   347
#> 8        TRUE  2246
#> 9        TRUE  5376
#> 10      FALSE  4545
#> 11      FALSE  9064
#> 12      FALSE   650
#> 13      FALSE   656
#> 14       TRUE  <NA>
#> 15       TRUE  <NA>
#> 16       TRUE  <NA>
#> 17       TRUE  <NA>
#> 18       TRUE  <NA>
#> 19       TRUE  <NA>
#> 20       TRUE  <NA>
#> 21      FALSE  <NA>
#> 22      FALSE  <NA>
#> 23      FALSE  <NA>
#> 24      FALSE  <NA>
#> 25      FALSE  <NA>
#> 26      FALSE  <NA>
#> 27       TRUE   289
#> 28       TRUE   117
#> 29         NA  8642
#> 30       TRUE   348
#> 31      FALSE  <NA>
#>                                                                                            description
#> 1                                                              Primary. The signature Notre Dame Blue.
#> 2                                                               Primary. The metallic Notre Dame Gold.
#> 3                                              Secondary. A deep blue between ND Blue and Bright Blue.
#> 4                                                                        Secondary. A bright mid blue.
#> 5                                                                     Secondary. A muted, darker gold.
#> 6                                    Secondary. A saturated gold; the anchor gold of the data palette.
#> 7                                                                     Secondary. The Notre Dame green.
#> 8                                                                       Secondary. A pale, soft green.
#> 9                                      Secondary. The one sky blue dark enough to use as a data color.
#> 10                                                 Near-white tint. A warm cream for page backgrounds.
#> 11                                                              Near-white tint. A lighter warm cream.
#> 12                                                                       Near-white tint. A pale blue.
#> 13                                                            Near-white tint. The lightest pale blue.
#> 14                                                   Former secondary Notre Dame color. A bright teal.
#> 15                               Former secondary Notre Dame color. A deep maroon (dark brownish red).
#> 16 Former tertiary Notre Dame color; the only former color of the tertiary tier. A dark violet purple.
#> 17                                            Former secondary Notre Dame color. A yellow-green olive.
#> 18                                              Former secondary Notre Dame color. A dark olive green.
#> 19                                        Former secondary Notre Dame color. A soft chartreuse yellow.
#> 20                                               Former secondary Notre Dame color. A warm near-black.
#> 21                      Informal background (not an ND brand color). A warm white just off pure white.
#> 22                                     Informal background (not an ND brand color). A soft warm white.
#> 23                     Informal background (not an ND brand color). A soft yellow just off pure white.
#> 24                             Informal background (not an ND brand color). A light, soft warm yellow.
#> 25                                         Informal background (not an ND brand color). A soft yellow.
#> 26                                  Informal background (not an ND brand color). A warmer soft yellow.
#> 27                                             Athletics. Notre Dame Blue, shared with the University.
#> 28                                                           Athletics. The warmer Standard Dome Gold.
#> 29                          Athletics. Metallic Dome Gold; a Pantone metallic ink with no digital hex.
#> 30                                 Athletics. Irish Green, one Pantone step from the University green.
#> 31                                                                                   Athletics. White.
# The two current primaries, and a former color, by name.
nd_color("navy", "metallic_gold", "maroon")
#> [1] "#0c2340" "#ae9142" "#4c1c2a"

# A whole role group.
nd_color(role = "tint")
#> [1] "#efe9d9" "#f8f4ec" "#e1e8f2" "#edf2f9"