flowchart TD A[Shiny Application] --> B[Bootstrap Framework] B --> C[Default Shiny Styles] C --> D[Custom CSS] D --> E[Inline Styles] F[bslib Themes] --> B G[External CSS Files] --> D H[tags$style] --> D I[HTML Attributes] --> E style A fill:#e1f5fe style B fill:#f3e5f5 style D fill:#e8f5e8 style F fill:#fff3e0
Key Takeaways
- Professional Visual Identity: Transform basic Shiny apps into branded, visually compelling applications that reflect organizational identity and engage users
- Modern Design Systems: Master Bootstrap 5, bslib themes, and contemporary design patterns that make your applications feel current and professional
- Custom CSS Mastery: Implement sophisticated styling techniques, from simple color changes to complex layouts and animations
- Responsive Design Excellence: Create applications that look outstanding across all devices with mobile-first design principles
- Brand Integration Strategy: Seamlessly incorporate logos, color schemes, typography, and visual elements that align with your brand guidelines
Introduction
The difference between a functional Shiny application and a truly professional one often lies in its visual design and user experience. While your application’s analytical capabilities might be sophisticated, users form their first impressions within seconds based on visual appeal, branding consistency, and overall design quality.
Modern users expect polished, branded experiences that feel as professional as commercial web applications. This comprehensive guide teaches you to transform basic Shiny applications into visually stunning, branded experiences using CSS styling, Bootstrap themes, and modern design systems. You’ll learn to implement consistent color schemes, typography, custom components, and responsive layouts that work beautifully across all devices.
Whether you’re building internal business dashboards, client-facing analytics tools, or public-facing applications, mastering Shiny’s styling capabilities will elevate your work from functional to exceptional, creating applications that users genuinely enjoy using.
Understanding Shiny’s Styling Architecture
Before diving into specific styling techniques, it’s essential to understand how Shiny handles CSS, Bootstrap integration, and the styling hierarchy that determines how your customizations are applied.
The Styling Hierarchy
Shiny applications follow a specific styling hierarchy that determines which styles take precedence:
- Browser Defaults - Basic HTML element styling
- Bootstrap Framework - Grid system, components, utilities
- Shiny Default Styles - Component-specific Shiny styling
- Custom CSS Files - Your external stylesheets
- Embedded CSS - Styles defined within your application
- Inline Styles - Element-specific style attributes
Understanding this hierarchy helps you override styles effectively and troubleshoot styling conflicts.
Bootstrap Integration in Shiny
Shiny is built on Bootstrap, providing a solid foundation for responsive design and professional components:
# Shiny automatically includes Bootstrap
fluidPage(
# Bootstrap classes work automatically
div(class = "container-fluid",
div(class = "row",
div(class = "col-md-6", "Left column"),
div(class = "col-md-6", "Right column")
)
) )
Bootstrap Benefits in Shiny:
- Responsive Grid System: Automatic mobile-friendly layouts
- Pre-built Components: Professional buttons, forms, navigation
- Utility Classes: Spacing, colors, typography without custom CSS
- Consistent Design Language: Professional appearance out of the box
Great styling starts with solid layout structure:
Before applying themes and custom styling, ensure your application has a well-organized layout structure. Understanding how elements are arranged and how the Bootstrap grid system works will help you apply styles more effectively and avoid layout conflicts.
Design your layout structure using our Interactive Layout Builder →
Practice with professional layout patterns, understand grid organization, and see how different structures affect styling - then return to implement beautiful themes that enhance rather than fight your layout.
Modern Theme Systems with bslib
The bslib
package revolutionizes Shiny theming by providing access to modern Bootstrap themes and custom theme creation tools.
Getting Started with bslib
Install and explore bslib’s capabilities:
# Install bslib if not already installed
if (!require(bslib)) install.packages("bslib")
library(bslib)
library(shiny)
Pre-built Bootstrap Themes
bslib
provides access to professional themes from Bootswatch:
library(bslib)
# Modern dark theme
<- fluidPage(
ui theme = bs_theme(bootswatch = "darkly"),
titlePanel("Dark Theme Application"),
sidebarLayout(
sidebarPanel(
sliderInput("n", "Number of points:", 1, 100, 50),
selectInput("color", "Color scheme:",
choices = c("blue", "red", "green"))
),mainPanel(
plotOutput("plot"),
verbatimTextOutput("summary")
)
) )
# Professional business theme
<- fluidPage(
ui theme = bs_theme(bootswatch = "flatly"),
# Custom navbar for corporate feel
navbarPage(
title = "Corporate Analytics Platform",
theme = bs_theme(
bootswatch = "flatly",
primary = "#2C3E50", # Corporate blue
secondary = "#95A5A6", # Professional gray
base_font = font_google("Open Sans")
),
tabPanel("Dashboard",
# Dashboard content
),tabPanel("Reports",
# Reports content
),tabPanel("Settings",
# Settings content
)
) )
# Vibrant, modern theme
<- fluidPage(
ui theme = bs_theme(
bootswatch = "pulse",
primary = "#E91E63", # Bright pink
secondary = "#9C27B0", # Purple
success = "#4CAF50", # Green
bg = "#FAFAFA", # Light background
fg = "#212121" # Dark text
),
titlePanel("Creative Data Explorer"),
# Application content with modern styling
)
Theme Comparison & Selection Demo
Explore Bootstrap themes using the professional bslib theme panel:
- Try quick presets - Click theme buttons for instant changes
- Use the theme panel - Professional customization sidebar (right side)
- Test dark mode - Toggle dark theme variants
- Export your theme - Generate R code for your selection
For detailed color and font customization: Use the Advanced Theme Customization Lab below for precise control over colors, typography, and styling options.
Key Learning: Experience bslib’s professional theme system - the industry standard for Shiny theme development. Perfect for theme discovery and quick selection.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 1500
library(shiny)
library(bslib)
library(bsicons)
ui <- fluidPage(
theme = bs_theme(version = 5, bootswatch = "cosmo"),
titlePanel(
div(
bs_icon("palette-fill", size = "1.2em"),
"Theme Comparison & Selection",
style = "display: flex; align-items: center; gap: 10px;"
)
),
# Info panel
div(class = "alert alert-info mb-4", role = "alert",
div(style = "display: flex; align-items: center; gap: 15px;",
bs_icon("info-circle", size = "2em", class = "text-primary"),
div(
h5("Professional Theme Browser", style = "margin: 0; color: #0c5aa6;"),
p("Browse and compare Bootstrap themes using the professional theme panel on the right. Perfect for quickly finding the right theme for your project.", style = "margin: 5px 0 0 0;")
)
)
),
# Quick preset buttons
div(class = "card mb-4",
div(class = "card-header",
h5(bs_icon("grid-3x3"), "Popular Theme Presets", style = "margin: 0; display: flex; align-items: center; gap: 8px;")
),
div(class = "card-body",
p("Click any theme below for instant preview, then use the theme panel on the right for fine-tuning:"),
div(class = "row g-3",
div(class = "col-md-2 col-sm-4 col-6",
actionButton("preset_cosmo", "Cosmo", class = "btn-outline-primary w-100")
),
div(class = "col-md-2 col-sm-4 col-6",
actionButton("preset_flatly", "Flatly", class = "btn-outline-success w-100")
),
div(class = "col-md-2 col-sm-4 col-6",
actionButton("preset_pulse", "Pulse", class = "btn-outline-secondary w-100")
),
div(class = "col-md-2 col-sm-4 col-6",
actionButton("preset_journal", "Journal", class = "btn-outline-danger w-100")
),
div(class = "col-md-2 col-sm-4 col-6",
actionButton("preset_yeti", "Yeti", class = "btn-outline-info w-100")
),
div(class = "col-md-2 col-sm-4 col-6",
actionButton("preset_spacelab", "Spacelab", class = "btn-outline-warning w-100")
),
div(class = "col-md-2 col-sm-4 col-6",
actionButton("preset_sandstone", "Sandstone", class = "btn-outline-secondary w-100")
),
div(class = "col-md-2 col-sm-4 col-6",
actionButton("preset_united", "United", class = "btn-outline-danger w-100")
),
div(class = "col-md-2 col-sm-4 col-6",
actionButton("preset_simplex", "Simplex", class = "btn-outline-warning w-100")
),
div(class = "col-md-2 col-sm-4 col-6",
actionButton("preset_litera", "Litera", class = "btn-outline-info w-100")
),
div(class = "col-md-2 col-sm-4 col-6",
actionButton("preset_lumen", "Lumen", class = "btn-outline-primary w-100")
),
div(class = "col-md-2 col-sm-4 col-6",
actionButton("export_theme", div(bs_icon("download"), " Export"), class = "btn-success w-100")
)
)
)
),
# Theme showcase content
div(class = "card",
div(class = "card-header",
h5(bs_icon("eye"), "Theme Preview", style = "margin: 0; display: flex; align-items: center; gap: 8px;")
),
div(class = "card-body",
h3("Theme Showcase Dashboard"),
p("This preview demonstrates how your selected theme affects all common UI components."),
# Navigation
div(class = "nav nav-pills mb-4",
tags$a(class = "nav-link active", "Overview"),
tags$a(class = "nav-link", "Components"),
tags$a(class = "nav-link", "Typography"),
tags$a(class = "nav-link", "Colors")
),
# Buttons showcase
div(class = "mb-4",
h5("Button Collection"),
div(class = "btn-toolbar gap-2", role = "toolbar",
div(class = "btn-group", role = "group",
tags$button(type = "button", class = "btn btn-primary", "Primary"),
tags$button(type = "button", class = "btn btn-secondary", "Secondary"),
tags$button(type = "button", class = "btn btn-success", "Success")
),
div(class = "btn-group", role = "group",
tags$button(type = "button", class = "btn btn-danger", "Danger"),
tags$button(type = "button", class = "btn btn-warning", "Warning"),
tags$button(type = "button", class = "btn btn-info", "Info")
)
),
div(class = "btn-toolbar gap-2 mt-2", role = "toolbar",
div(class = "btn-group", role = "group",
tags$button(type = "button", class = "btn btn-outline-primary", "Outline Primary"),
tags$button(type = "button", class = "btn btn-outline-success", "Outline Success"),
tags$button(type = "button", class = "btn btn-outline-danger", "Outline Danger")
)
)
),
# Cards and forms
div(class = "row mb-4",
div(class = "col-md-6",
h5("Form Components"),
div(class = "mb-3",
tags$label(class = "form-label", "Email Address:"),
tags$input(type = "email", class = "form-control", value = "user@example.com")
),
div(class = "mb-3",
tags$label(class = "form-label", "Select Category:"),
tags$select(class = "form-select",
tags$option("Design"),
tags$option("Development", selected = TRUE),
tags$option("Marketing")
)
),
div(class = "mb-3",
tags$label(class = "form-label", "Project Priority:"),
tags$input(type = "range", class = "form-range", value = "75")
),
div(class = "form-check",
tags$input(class = "form-check-input", type = "checkbox", checked = TRUE),
tags$label(class = "form-check-label", "Send notifications")
)
),
div(class = "col-md-6",
h5("Metric Cards"),
div(class = "row g-3",
div(class = "col-6",
div(class = "card text-center",
div(class = "card-body",
h4("1,247", class = "text-primary"),
p("Active Users", class = "card-text text-muted", style = "margin: 0;")
)
)
),
div(class = "col-6",
div(class = "card text-center",
div(class = "card-body",
h4("98.5%", class = "text-success"),
p("Uptime", class = "card-text text-muted", style = "margin: 0;")
)
)
),
div(class = "col-6",
div(class = "card text-center",
div(class = "card-body",
h4("$12.4K", class = "text-warning"),
p("Revenue", class = "card-text text-muted", style = "margin: 0;")
)
)
),
div(class = "col-6",
div(class = "card text-center",
div(class = "card-body",
h4("4.8★", class = "text-info"),
p("Rating", class = "card-text text-muted", style = "margin: 0;")
)
)
)
)
)
),
# Alerts
div(class = "mb-4",
h5("Alert Messages"),
div(class = "alert alert-primary", role = "alert",
bs_icon("info-circle"), " Primary alert: Your theme is looking great!"
),
div(class = "alert alert-success", role = "alert",
bs_icon("check-circle"), " Success alert: Theme selection completed successfully."
),
div(class = "alert alert-warning", role = "alert",
bs_icon("exclamation-triangle"), " Warning alert: Consider testing on different screen sizes."
)
),
# Progress and badges
div(class = "row",
div(class = "col-md-6",
h5("Progress Tracking"),
div(class = "mb-3",
div(class = "d-flex justify-content-between align-items-center mb-1",
tags$small("Theme Selection"),
tags$small("85%")
),
div(class = "progress",
div(class = "progress-bar", style = "width: 85%")
)
),
div(class = "mb-3",
div(class = "d-flex justify-content-between align-items-center mb-1",
tags$small("Customization"),
tags$small("60%")
),
div(class = "progress",
div(class = "progress-bar bg-success", style = "width: 60%")
)
)
),
div(class = "col-md-6",
h5("Status Indicators"),
p(
"Status: ", tags$span(class = "badge bg-success", "Online"), " ",
"Priority: ", tags$span(class = "badge bg-warning", "High"), " ",
"Version: ", tags$span(class = "badge bg-secondary", "v2.0")
),
p(
"Categories: ",
tags$span(class = "badge bg-primary", "Design"), " ",
tags$span(class = "badge bg-info", "Frontend"), " ",
tags$span(class = "badge bg-dark", "Bootstrap")
)
)
)
)
),
# Usage instructions
div(class = "card mt-4",
div(class = "card-header",
h6(bs_icon("question-circle"), "How to Use the Theme Browser", style = "margin: 0; display: flex; align-items: center; gap: 8px;")
),
div(class = "card-body",
div(class = "row",
div(class = "col-md-6",
h6("Theme Selection:"),
tags$ul(
tags$li("Click preset buttons above for instant theme changes"),
tags$li("Use the theme panel on the right for more options"),
tags$li("Browse 'Preset theme' dropdown for all available themes"),
tags$li("Toggle 'Preview dark mode' to test dark variants")
)
),
div(class = "col-md-6",
h6("Fine-tuning:"),
tags$ul(
tags$li("Adjust colors using the theme panel sliders"),
tags$li("Experiment with different font combinations"),
tags$li("Test your theme across all components above"),
tags$li("Export your final theme using the green button")
)
)
)
)
)
)
server <- function(input, output, session) {
# Enable bslib themer - this creates the professional theme panel
bs_themer()
# Theme preset buttons
observeEvent(input$preset_cosmo, {
session$setCurrentTheme(bs_theme(version = 5, bootswatch = "cosmo"))
})
observeEvent(input$preset_flatly, {
session$setCurrentTheme(bs_theme(version = 5, bootswatch = "flatly"))
})
observeEvent(input$preset_pulse, {
session$setCurrentTheme(bs_theme(version = 5, bootswatch = "pulse"))
})
observeEvent(input$preset_journal, {
session$setCurrentTheme(bs_theme(version = 5, bootswatch = "journal"))
})
observeEvent(input$preset_yeti, {
session$setCurrentTheme(bs_theme(version = 5, bootswatch = "yeti"))
})
observeEvent(input$preset_spacelab, {
session$setCurrentTheme(bs_theme(version = 5, bootswatch = "spacelab"))
})
observeEvent(input$preset_sandstone, {
session$setCurrentTheme(bs_theme(version = 5, bootswatch = "sandstone"))
})
observeEvent(input$preset_united, {
session$setCurrentTheme(bs_theme(version = 5, bootswatch = "united"))
})
observeEvent(input$preset_simplex, {
session$setCurrentTheme(bs_theme(version = 5, bootswatch = "simplex"))
})
observeEvent(input$preset_litera, {
session$setCurrentTheme(bs_theme(version = 5, bootswatch = "litera"))
})
observeEvent(input$preset_lumen, {
session$setCurrentTheme(bs_theme(version = 5, bootswatch = "lumen"))
})
# Export functionality
observeEvent(input$export_theme, {
showModal(modalDialog(
title = div(bs_icon("download"), "Export Selected Theme", style = "display: flex; align-items: center; gap: 10px;"),
size = "l",
div(
div(class = "alert alert-success",
bs_icon("check-circle"), " Your theme is ready to export! Copy the code below to use in your projects."
),
h6("Theme Export Code:"),
tags$pre(style = "background: #f8f9fa; padding: 15px; border-radius: 5px; border: 1px solid #dee2e6; max-height: 300px; overflow-y: auto;",
code(id = "export-code",
"library(shiny)\n",
"library(bslib)\n\n",
"# Your selected theme\n",
"my_theme <- bs_theme(\n",
" version = 5,\n",
" bootswatch = \"cosmo\", # Change this to your selected theme\n",
" \n",
" # Add custom colors if you modified them in the theme panel:\n",
" # primary = \"#your-color\",\n",
" # secondary = \"#your-color\",\n",
" # etc...\n",
")\n\n",
"# Use in your Shiny app:\n",
"ui <- fluidPage(\n",
" theme = my_theme,\n",
" # Your content here\n",
")\n\n",
"server <- function(input, output, session) {\n",
" # Your server logic\n",
"}\n\n",
"shinyApp(ui, server)"
)
),
p(tags$strong("Pro tip:"), " If you customized colors using the theme panel, note down the color values and add them to the theme definition above.")
),
footer = tagList(
modalButton("Close"),
tags$button(
type = "button",
class = "btn btn-primary",
onclick = "navigator.clipboard.writeText(document.getElementById('export-code').textContent);
this.innerHTML = '✓ Copied!';
this.classList.add('btn-success');
this.classList.remove('btn-primary');
setTimeout(() => {
this.innerHTML = 'Copy Code';
this.classList.add('btn-primary');
this.classList.remove('btn-success');
}, 2000);",
"Copy Code"
)
)
))
})
}
shinyApp(ui = ui, server = server)
Custom Theme Creation
Create completely custom themes that match your brand:
# Define custom corporate theme
<- bs_theme(
corporate_theme # Brand colors
primary = "#1E88E5", # Company blue
secondary = "#546E7A", # Professional gray
success = "#43A047", # Success green
info = "#00ACC1", # Info cyan
warning = "#FB8C00", # Warning orange
danger = "#E53935", # Error red
# Background and text
bg = "#FFFFFF", # White background
fg = "#212121", # Dark text
# Typography
base_font = font_google("Roboto"),
heading_font = font_google("Roboto Condensed"),
code_font = font_google("JetBrains Mono"),
# Component sizing
"input-btn-padding-y" = ".5rem",
"input-btn-padding-x" = "1rem"
)
<- fluidPage(
ui theme = corporate_theme,
# Your application content
)
Advanced Custom Theme Workshop
Create completely custom branded themes with precise control:
- Start with base theme - Choose foundation from dropdown
- Customize color palette - Adjust all semantic colors
- Generate random colors - Explore harmonious color combinations
- Select typography - Choose from professional font options
- Preview live changes - See real-time updates across all components
- Export custom theme - Get production-ready R code
Key Learning: Master advanced theme customization for brand-specific designs and enterprise applications.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 1500
library(shiny)
library(bslib)
library(bsicons)
# Available themes for base selection
available_themes <- c(
"Default" = "default",
"Bootstrap" = "bootstrap",
"Cerulean" = "cerulean",
"Cosmo" = "cosmo",
"Flatly" = "flatly",
"Journal" = "journal",
"Litera" = "litera",
"Lumen" = "lumen",
"Pulse" = "pulse",
"Sandstone" = "sandstone",
"Simplex" = "simplex",
"Spacelab" = "spacelab",
"United" = "united",
"Yeti" = "yeti"
)
# Google Fonts options
font_options <- c(
"Default" = "default",
"Inter" = "Inter",
"Open Sans" = "Open Sans",
"Roboto" = "Roboto",
"Lato" = "Lato",
"Montserrat" = "Montserrat",
"Source Sans Pro" = "Source Sans Pro",
"Poppins" = "Poppins"
)
# Theme defaults for each theme
theme_defaults <- list(
"default" = list(primary = "#0d6efd", secondary = "#6c757d", success = "#198754", danger = "#dc3545", warning = "#ffc107", info = "#0dcaf0", bg = "#ffffff", fg = "#212529"),
"bootstrap" = list(primary = "#0d6efd", secondary = "#6c757d", success = "#198754", danger = "#dc3545", warning = "#ffc107", info = "#0dcaf0", bg = "#ffffff", fg = "#212529"),
"cerulean" = list(primary = "#2fa4e7", secondary = "#6c757d", success = "#73a839", danger = "#c71c22", warning = "#dd5600", info = "#033c73", bg = "#ffffff", fg = "#212529"),
"cosmo" = list(primary = "#2780e3", secondary = "#6c757d", success = "#3fb618", danger = "#ff0039", warning = "#ff7518", info = "#9954bb", bg = "#ffffff", fg = "#212529"),
"flatly" = list(primary = "#2c3e50", secondary = "#95a5a6", success = "#18bc9c", danger = "#e74c3c", warning = "#f39c12", info = "#3498db", bg = "#ffffff", fg = "#212529"),
"journal" = list(primary = "#eb6864", secondary = "#6c757d", success = "#22b24c", danger = "#f93f37", warning = "#f5e625", info = "#369", bg = "#ffffff", fg = "#212529"),
"litera" = list(primary = "#4582ec", secondary = "#adb5bd", success = "#02b875", danger = "#f0506e", warning = "#fd7e14", info = "#17a2b8", bg = "#ffffff", fg = "#212529"),
"lumen" = list(primary = "#158cba", secondary = "#6c757d", success = "#28a745", danger = "#dc3545", warning = "#ffc107", info = "#17a2b8", bg = "#ffffff", fg = "#212529"),
"pulse" = list(primary = "#593196", secondary = "#6c757d", success = "#13b955", danger = "#da1414", warning = "#efa31d", info = "#009cdc", bg = "#ffffff", fg = "#212529"),
"sandstone" = list(primary = "#325d88", secondary = "#8e8c84", success = "#93c54b", danger = "#d9534f", warning = "#f47c3c", info = "#29abe0", bg = "#ffffff", fg = "#212529"),
"simplex" = list(primary = "#d9230f", secondary = "#6c757d", success = "#469408", danger = "#d9534f", warning = "#ffc107", info = "#029acf", bg = "#ffffff", fg = "#212529"),
"spacelab" = list(primary = "#446e9b", secondary = "#6c757d", success = "#3cb521", danger = "#cd0200", warning = "#d47500", info = "#3399f3", bg = "#ffffff", fg = "#212529"),
"united" = list(primary = "#dd4814", secondary = "#6c757d", success = "#38b44a", danger = "#df382c", warning = "#efb73e", info = "#19b6ee", bg = "#ffffff", fg = "#212529"),
"yeti" = list(primary = "#008cba", secondary = "#6c757d", success = "#43ac6a", danger = "#f04124", warning = "#e99002", info = "#5bc0de", bg = "#ffffff", fg = "#212529")
)
ui <- fluidPage(
theme = bs_theme(version = 5, bootswatch = "cosmo"),
titlePanel(
div(
bs_icon("brush-fill", size = "1.2em"),
"Advanced Theme Customization Lab",
style = "display: flex; align-items: center; gap: 10px;"
)
),
# Info panel
div(class = "alert alert-warning mb-4", role = "alert",
div(style = "display: flex; align-items: center; gap: 15px;",
bs_icon("tools", size = "2em", class = "text-warning"),
div(
h5("Advanced Customization Workshop", style = "margin: 0; color: #856404;"),
p("Create completely custom themes with precise control over colors, typography, and styling. Perfect for brand-specific designs and detailed customization.", style = "margin: 5px 0 0 0;")
)
)
),
fluidRow(
# Control Panel
column(4,
div(class = "card mb-3",
div(class = "card-header",
h5(bs_icon("sliders"), "Customization Controls", style = "margin: 0; display: flex; align-items: center; gap: 8px;")
),
div(class = "card-body",
# Base theme selection
selectInput("base_theme",
label = div(bs_icon("brush"), "Base Theme:"),
choices = available_themes,
selected = "cosmo"),
tags$small("Start with a base theme, then customize colors below.", class = "text-muted"),
hr(),
# Primary colors
h6(bs_icon("palette"), "Primary Colors:"),
div(class = "row g-2",
div(class = "col-6",
colourpicker::colourInput("primary_color", "Primary:", value = "#2780e3")
),
div(class = "col-6",
colourpicker::colourInput("secondary_color", "Secondary:", value = "#6c757d")
)
),
# Semantic colors
h6(bs_icon("exclamation-triangle"), "Semantic Colors:", class = "mt-3"),
div(class = "row g-2",
div(class = "col-6",
colourpicker::colourInput("success_color", "Success:", value = "#3fb618")
),
div(class = "col-6",
colourpicker::colourInput("danger_color", "Danger:", value = "#ff0039")
)
),
div(class = "row g-2 mt-1",
div(class = "col-6",
colourpicker::colourInput("warning_color", "Warning:", value = "#ff7518")
),
div(class = "col-6",
colourpicker::colourInput("info_color", "Info:", value = "#9954bb")
)
),
hr(),
# Typography
h6(bs_icon("type"), "Typography:"),
selectInput("font_family",
label = "Base Font:",
choices = font_options,
selected = "default"),
hr(),
# Background colors
h6(bs_icon("square"), "Background & Text:"),
div(class = "row g-2",
div(class = "col-6",
colourpicker::colourInput("bg_color", "Background:", value = "#ffffff")
),
div(class = "col-6",
colourpicker::colourInput("fg_color", "Text Color:", value = "#212529")
)
),
hr(),
# Action buttons
div(class = "d-grid gap-2",
actionButton("reset_theme", "Reset to Base Theme",
class = "btn-outline-secondary"),
actionButton("export_theme", "Export Custom Theme",
class = "btn-success")
)
)
)
),
# Preview Panel
column(8,
div(class = "card",
div(class = "card-header",
h5(bs_icon("eye"), "Live Customization Preview", style = "margin: 0; display: flex; align-items: center; gap: 8px;")
),
div(class = "card-body",
div(id = "theme-preview-container",
uiOutput("theme_preview")
)
)
)
)
),
# Generated CSS display
fluidRow(
column(12,
div(class = "card mt-3",
div(class = "card-header",
h6(bs_icon("code-square"), "Generated Theme Code", style = "margin: 0; display: flex; align-items: center; gap: 8px;")
),
div(class = "card-body",
verbatimTextOutput("theme_css_output")
)
)
)
)
)
server <- function(input, output, session) {
# Update color inputs when base theme changes
observeEvent(input$base_theme, {
defaults <- theme_defaults[[input$base_theme]]
if (!is.null(defaults)) {
updateColourInput(session, "primary_color", value = defaults$primary)
updateColourInput(session, "secondary_color", value = defaults$secondary)
updateColourInput(session, "success_color", value = defaults$success)
updateColourInput(session, "danger_color", value = defaults$danger)
updateColourInput(session, "warning_color", value = defaults$warning)
updateColourInput(session, "info_color", value = defaults$info)
updateColourInput(session, "bg_color", value = defaults$bg)
updateColourInput(session, "fg_color", value = defaults$fg)
}
})
# Reset to theme defaults
observeEvent(input$reset_theme, {
defaults <- theme_defaults[[input$base_theme]]
if (!is.null(defaults)) {
updateColourInput(session, "primary_color", value = defaults$primary)
updateColourInput(session, "secondary_color", value = defaults$secondary)
updateColourInput(session, "success_color", value = defaults$success)
updateColourInput(session, "danger_color", value = defaults$danger)
updateColourInput(session, "warning_color", value = defaults$warning)
updateColourInput(session, "info_color", value = defaults$info)
updateColourInput(session, "bg_color", value = defaults$bg)
updateColourInput(session, "fg_color", value = defaults$fg)
updateSelectInput(session, "font_family", selected = "default")
}
})
# Generate CSS for theme preview
preview_css <- reactive({
req(input$primary_color, input$secondary_color, input$success_color,
input$danger_color, input$warning_color, input$info_color,
input$bg_color, input$fg_color)
font_css <- if(input$font_family != "default") {
paste0("font-family: '", input$font_family, "', sans-serif;")
} else ""
paste0(
"#theme-preview-container {",
" background: ", input$bg_color, ";",
" color: ", input$fg_color, ";",
" ", font_css,
"}",
"#theme-preview-container .btn-primary {",
" background-color: ", input$primary_color, ";",
" border-color: ", input$primary_color, ";",
"}",
"#theme-preview-container .btn-secondary {",
" background-color: ", input$secondary_color, ";",
" border-color: ", input$secondary_color, ";",
"}",
"#theme-preview-container .btn-success {",
" background-color: ", input$success_color, ";",
" border-color: ", input$success_color, ";",
"}",
"#theme-preview-container .btn-danger {",
" background-color: ", input$danger_color, ";",
" border-color: ", input$danger_color, ";",
"}",
"#theme-preview-container .btn-warning {",
" background-color: ", input$warning_color, ";",
" border-color: ", input$warning_color, ";",
"}",
"#theme-preview-container .btn-info {",
" background-color: ", input$info_color, ";",
" border-color: ", input$info_color, ";",
"}",
"#theme-preview-container .form-control:focus {",
" border-color: ", input$primary_color, ";",
" box-shadow: 0 0 0 0.2rem rgba(", paste(col2rgb(input$primary_color), collapse = ","), ", 0.25);",
"}",
"#theme-preview-container .alert-primary {",
" background-color: rgba(", paste(col2rgb(input$primary_color), collapse = ","), ", 0.1);",
" border-color: rgba(", paste(col2rgb(input$primary_color), collapse = ","), ", 0.2);",
" color: ", input$primary_color, ";",
"}",
"#theme-preview-container .alert-success {",
" background-color: rgba(", paste(col2rgb(input$success_color), collapse = ","), ", 0.1);",
" border-color: rgba(", paste(col2rgb(input$success_color), collapse = ","), ", 0.2);",
" color: ", input$success_color, ";",
"}",
"#theme-preview-container .alert-warning {",
" background-color: rgba(", paste(col2rgb(input$warning_color), collapse = ","), ", 0.1);",
" border-color: rgba(", paste(col2rgb(input$warning_color), collapse = ","), ", 0.2);",
" color: ", input$warning_color, ";",
"}",
"#theme-preview-container .alert-danger {",
" background-color: rgba(", paste(col2rgb(input$danger_color), collapse = ","), ", 0.1);",
" border-color: rgba(", paste(col2rgb(input$danger_color), collapse = ","), ", 0.2);",
" color: ", input$danger_color, ";",
"}",
"#theme-preview-container .progress-bar {",
" background-color: ", input$primary_color, ";",
"}",
"#theme-preview-container .progress-bar.bg-success {",
" background-color: ", input$success_color, " !important;",
"}",
"#theme-preview-container .progress-bar.bg-warning {",
" background-color: ", input$warning_color, " !important;",
"}",
"#theme-preview-container .badge.bg-primary {",
" background-color: ", input$primary_color, " !important;",
"}",
"#theme-preview-container .badge.bg-success {",
" background-color: ", input$success_color, " !important;",
"}",
"#theme-preview-container .badge.bg-warning {",
" background-color: ", input$warning_color, " !important;",
"}",
"#theme-preview-container .badge.bg-info {",
" background-color: ", input$info_color, " !important;",
"}",
"#theme-preview-container .badge.bg-danger {",
" background-color: ", input$danger_color, " !important;",
"}",
"#theme-preview-container .text-primary {",
" color: ", input$primary_color, " !important;",
"}",
"#theme-preview-container .text-success {",
" color: ", input$success_color, " !important;",
"}",
"#theme-preview-container .text-warning {",
" color: ", input$warning_color, " !important;",
"}",
"#theme-preview-container .text-info {",
" color: ", input$info_color, " !important;",
"}",
"#theme-preview-container .card-header {",
" background: linear-gradient(135deg, ", input$primary_color, " 0%, ", input$secondary_color, " 100%);",
" color: white;",
"}",
"#theme-preview-container h3, #theme-preview-container h4, #theme-preview-container h5 {",
" color: ", input$primary_color, ";",
" ", font_css,
"}"
)
})
# Inject CSS dynamically
observe({
insertUI(
selector = "head",
where = "beforeEnd",
ui = tags$style(HTML(preview_css())),
immediate = TRUE
)
})
# Theme preview content
output$theme_preview <- renderUI({
div(class = "preview-content", style = "padding: 20px; border-radius: 8px; min-height: 500px;",
h3("Custom Theme Preview"),
p("See how your custom colors and typography work together across all components."),
# Color palette display
div(class = "mb-4",
h5("Your Color Palette"),
div(class = "row g-2",
div(class = "col-2",
div(style = paste0("background: ", input$primary_color, "; height: 60px; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 0.8rem;"), "Primary")
),
div(class = "col-2",
div(style = paste0("background: ", input$secondary_color, "; height: 60px; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 0.8rem;"), "Secondary")
),
div(class = "col-2",
div(style = paste0("background: ", input$success_color, "; height: 60px; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 0.8rem;"), "Success")
),
div(class = "col-2",
div(style = paste0("background: ", input$danger_color, "; height: 60px; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 0.8rem;"), "Danger")
),
div(class = "col-2",
div(style = paste0("background: ", input$warning_color, "; height: 60px; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 0.8rem;"), "Warning")
),
div(class = "col-2",
div(style = paste0("background: ", input$info_color, "; height: 60px; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 0.8rem;"), "Info")
)
)
),
# Button showcase
div(class = "mb-4",
h5("Button Components"),
div(class = "btn-group mb-2", role = "group",
tags$button(type = "button", class = "btn btn-primary", "Primary"),
tags$button(type = "button", class = "btn btn-secondary", "Secondary"),
tags$button(type = "button", class = "btn btn-success", "Success"),
tags$button(type = "button", class = "btn btn-danger", "Danger"),
tags$button(type = "button", class = "btn btn-warning", "Warning"),
tags$button(type = "button", class = "btn btn-info", "Info")
)
),
# Form components
div(class = "row mb-4",
div(class = "col-6",
h5("Form Elements"),
div(class = "mb-3",
tags$label(class = "form-label", "Sample Input:"),
tags$input(type = "text", class = "form-control",
placeholder = "Type here...", value = "Custom styled input")
),
div(class = "mb-3",
tags$label(class = "form-label", "Select Menu:"),
tags$select(class = "form-select",
tags$option("Option 1"),
tags$option("Option 2", selected = TRUE),
tags$option("Option 3")
)
),
div(class = "form-check",
tags$input(class = "form-check-input", type = "checkbox", checked = TRUE),
tags$label(class = "form-check-label", "Custom checkbox")
)
),
div(class = "col-6",
h5("Alert Messages"),
div(class = "alert alert-primary", role = "alert",
bs_icon("info-circle"), " Primary alert with custom styling."
),
div(class = "alert alert-success", role = "alert",
bs_icon("check-circle"), " Success message in your colors."
),
div(class = "alert alert-warning", role = "alert",
bs_icon("exclamation-triangle"), " Warning with custom palette."
)
)
),
# Progress bars and badges
div(class = "row mb-4",
div(class = "col-6",
h5("Progress Indicators"),
div(class = "mb-2",
tags$label("Primary Progress", class = "form-label"),
div(class = "progress", style = "height: 20px;",
div(class = "progress-bar", role = "progressbar",
style = "width: 75%", "75%")
)
),
div(class = "mb-2",
tags$label("Success Progress", class = "form-label"),
div(class = "progress", style = "height: 20px;",
div(class = "progress-bar bg-success", role = "progressbar",
style = "width: 60%", "60%")
)
),
div(class = "mb-2",
tags$label("Warning Progress", class = "form-label"),
div(class = "progress", style = "height: 20px;",
div(class = "progress-bar bg-warning", role = "progressbar",
style = "width: 40%", "40%")
)
)
),
div(class = "col-6",
h5("Status Badges"),
p(
"Status: ", tags$span(class = "badge bg-success", "Active"), " ",
"Priority: ", tags$span(class = "badge bg-warning", "High"), " ",
"Type: ", tags$span(class = "badge bg-primary", "Custom")
),
p(
"Categories: ",
tags$span(class = "badge bg-info", "Design"), " ",
tags$span(class = "badge bg-secondary", "Development"), " ",
tags$span(class = "badge bg-danger", "Testing")
)
)
),
# Sample card
div(class = "card",
div(class = "card-header",
h6(bs_icon("star"), "Custom Styled Card", style = "margin: 0; display: flex; align-items: center; gap: 8px;")
),
div(class = "card-body",
h4("Your Brand Colors", class = "text-primary"),
p("This card demonstrates how your custom color palette creates a cohesive design language throughout your application."),
div(class = "row",
div(class = "col-4 text-center",
h3("$45.2K", class = "text-success"),
p("Revenue", style = "margin: 0;")
),
div(class = "col-4 text-center",
h3("1,234", class = "text-info"),
p("Users", style = "margin: 0;")
),
div(class = "col-4 text-center",
h3("98.5%", class = "text-warning"),
p("Uptime", style = "margin: 0;")
)
)
)
)
)
})
# Export theme functionality
observeEvent(input$export_theme, {
showModal(modalDialog(
title = div(bs_icon("download"), "Export Your Custom Theme", style = "display: flex; align-items: center; gap: 10px;"),
size = "l",
div(
div(class = "alert alert-success",
bs_icon("check-circle"), " Your custom theme is ready! Copy the code below to use in your projects."
),
h6("Custom Theme R Code:"),
tags$pre(id = "custom-theme-code", style = "background: #f8f9fa; padding: 15px; border-radius: 5px; border: 1px solid #dee2e6; max-height: 400px; overflow-y: auto; font-size: 0.9rem;",
verbatimTextOutput("modal_theme_code")
)
),
footer = tagList(
modalButton("Close"),
tags$button(
type = "button",
class = "btn btn-primary",
id = "copy-theme-btn",
onclick = "
try {
// Get the text content from the verbatim output
const codeElement = document.querySelector('#custom-theme-code pre');
let textToCopy = '';
if (codeElement) {
textToCopy = codeElement.textContent || codeElement.innerText;
} else {
// Fallback: try to get from the container
const container = document.getElementById('custom-theme-code');
textToCopy = container ? (container.textContent || container.innerText) : '';
}
if (!textToCopy) {
alert('No code to copy. Please ensure the theme is generated.');
return;
}
// Modern clipboard API (preferred)
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(textToCopy).then(() => {
// Show success feedback
const btn = document.getElementById('copy-theme-btn');
const originalContent = btn.innerHTML;
btn.innerHTML = '<i class=\"bi bi-check\"></i> Copied!';
btn.classList.remove('btn-primary');
btn.classList.add('btn-success');
setTimeout(() => {
btn.innerHTML = originalContent;
btn.classList.remove('btn-success');
btn.classList.add('btn-primary');
}, 2000);
}).catch(err => {
console.error('Clipboard write failed:', err);
alert('Copy failed. Please try selecting and copying manually.');
});
} else {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = textToCopy;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
// Show success feedback
const btn = document.getElementById('copy-theme-btn');
const originalContent = btn.innerHTML;
btn.innerHTML = '<i class=\"bi bi-check\"></i> Copied!';
btn.classList.remove('btn-primary');
btn.classList.add('btn-success');
setTimeout(() => {
btn.innerHTML = originalContent;
btn.classList.remove('btn-success');
btn.classList.add('btn-primary');
}, 2000);
} else {
alert('Copy failed. Please try selecting and copying manually.');
}
} catch (err) {
console.error('execCommand failed:', err);
alert('Copy failed. Please try selecting and copying manually.');
}
document.body.removeChild(textArea);
}
} catch (error) {
console.error('Copy operation failed:', error);
alert('Copy failed. Please try selecting and copying manually.');
}
",
div(bs_icon("clipboard"), " Copy to Clipboard")
)
)
))
})
# CSS output for the main page
output$theme_css_output <- renderText({
req(input$primary_color, input$secondary_color, input$success_color,
input$danger_color, input$warning_color, input$info_color,
input$bg_color, input$fg_color)
base_theme_line <- if(input$base_theme != "default") {
paste0(' bootswatch = "', input$base_theme, '",\n')
} else ""
font_line <- if(input$font_family != "default") {
paste0(' base_font = font_google("', input$font_family, '"),\n')
} else ""
paste0(
"# Your Custom Theme\n",
"library(shiny)\n",
"library(bslib)\n\n",
"my_custom_theme <- bs_theme(\n",
" version = 5,\n",
base_theme_line,
" primary = \"", input$primary_color, "\",\n",
" secondary = \"", input$secondary_color, "\",\n",
" success = \"", input$success_color, "\",\n",
" danger = \"", input$danger_color, "\",\n",
" warning = \"", input$warning_color, "\",\n",
" info = \"", input$info_color, "\",\n",
" bg = \"", input$bg_color, "\",\n",
" fg = \"", input$fg_color, "\",\n",
font_line,
")\n\n",
"# Use in your Shiny app:\n",
"fluidPage(\n",
" theme = my_custom_theme,\n",
" # Your UI content here\n",
")"
)
})
# Modal theme code (same as above)
output$modal_theme_code <- renderText({
req(input$primary_color, input$secondary_color, input$success_color,
input$danger_color, input$warning_color, input$info_color,
input$bg_color, input$fg_color)
base_theme_line <- if(input$base_theme != "default") {
paste0(' bootswatch = "', input$base_theme, '",\n')
} else ""
font_line <- if(input$font_family != "default") {
paste0(' base_font = font_google("', input$font_family, '"),\n')
} else ""
paste0(
"library(shiny)\n",
"library(bslib)\n\n",
"# Your custom theme\n",
"my_custom_theme <- bs_theme(\n",
" version = 5,\n",
base_theme_line,
" primary = \"", input$primary_color, "\",\n",
" secondary = \"", input$secondary_color, "\",\n",
" success = \"", input$success_color, "\",\n",
" danger = \"", input$danger_color, "\",\n",
" warning = \"", input$warning_color, "\",\n",
" info = \"", input$info_color, "\",\n",
" bg = \"", input$bg_color, "\",\n",
" fg = \"", input$fg_color, "\",\n",
font_line,
")\n\n",
"# Use in your Shiny application:\n",
"ui <- fluidPage(\n",
" theme = my_custom_theme,\n",
" titlePanel(\"My Custom Themed App\"),\n",
" # Your content here\n",
")\n\n",
"server <- function(input, output, session) {\n",
" # Your server logic\n",
"}\n\n",
"shinyApp(ui = ui, server = server)"
)
})
}
shinyApp(ui = ui, server = server)
Brand Integration Workshop
Build a comprehensive brand system from scratch:
- Define brand identity - Set primary colors, typography, and visual style
- Create color systems - Build semantic color palettes with proper contrast
- Implement typography scales - Establish heading hierarchy and text styles
- Design component library - See how brand choices affect all UI elements
- Export brand tokens - Generate CSS variables for reuse across applications
Key Learning: Systematic brand implementation creates cohesive user experiences that build trust and professional credibility across your entire application portfolio.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 1500
library(shiny)
library(bslib)
library(bsicons)
library(colourpicker)
# Brand presets for demonstration
brand_presets <- list(
"Custom" = list(
primary = "#0d6efd", secondary = "#6c757d", accent = "#20c997",
font = "Inter", logo_text = "Your Brand"
),
"Tech Startup" = list(
primary = "#6366f1", secondary = "#8b5cf6", accent = "#06b6d4",
font = "Roboto", logo_text = "TechCorp"
),
"Financial Services" = list(
primary = "#1e40af", secondary = "#374151", accent = "#059669",
font = "Source Sans Pro", logo_text = "FinanceOne"
),
"Healthcare" = list(
primary = "#0ea5e9", secondary = "#64748b", accent = "#10b981",
font = "Open Sans", logo_text = "HealthPlus"
),
"Creative Agency" = list(
primary = "#ec4899", secondary = "#a855f7", accent = "#f59e0b",
font = "Montserrat", logo_text = "CreativeStudio"
)
)
ui <- fluidPage(
theme = bs_theme(version = 5, bootswatch = "cosmo"),
titlePanel(
div(
bs_icon("building", size = "1.2em"),
"Brand Integration Workshop",
style = "display: flex; align-items: center; gap: 10px;"
)
),
fluidRow(
# Brand Controls
column(3,
div(class = "card",
div(class = "card-header",
h6(bs_icon("palette2"), "Brand Controls", style = "margin: 0;")
),
div(class = "card-body",
selectInput("brand_preset",
"Brand Preset:",
choices = names(brand_presets),
selected = "Tech Startup"),
hr(),
textInput("company_name", "Company Name:",
value = "TechCorp"),
textInput("tagline", "Tagline:",
value = "Innovation Simplified"),
hr(),
h6("Color Palette:"),
colourInput("brand_primary", "Primary:", "#6366f1"),
colourInput("brand_secondary", "Secondary:", "#8b5cf6"),
colourInput("brand_accent", "Accent:", "#06b6d4"),
hr(),
selectInput("brand_font", "Typography:",
choices = c("Inter", "Roboto", "Open Sans",
"Source Sans Pro", "Montserrat", "Lato"),
selected = "Roboto"),
hr(),
checkboxInput("use_gradients", "Use Gradients", TRUE),
checkboxInput("rounded_corners", "Rounded Design", TRUE)
)
)
),
# Brand Preview
column(9,
div(class = "card",
div(class = "card-header",
h6(bs_icon("eye"), "Brand Implementation Preview", style = "margin: 0;")
),
div(class = "card-body p-0",
uiOutput("brand_preview")
)
)
)
),
# Export Section
fluidRow(
column(12,
div(class = "card mt-3",
div(class = "card-header d-flex justify-content-between align-items-center",
h6(bs_icon("download"), "Export Brand System", style = "margin: 0;"),
actionButton("export_brand", "Generate Brand Package", class = "btn-success")
),
div(class = "card-body",
verbatimTextOutput("css_export")
)
)
)
)
)
server <- function(input, output, session) {
# Update inputs when preset changes
observe({
preset <- brand_presets[[input$brand_preset]]
if (!is.null(preset) && input$brand_preset != "Custom") {
updateColourInput(session, "brand_primary", value = preset$primary)
updateColourInput(session, "brand_secondary", value = preset$secondary)
updateColourInput(session, "brand_accent", value = preset$accent)
updateSelectInput(session, "brand_font", selected = preset$font)
updateTextInput(session, "company_name", value = preset$logo_text)
}
})
# Brand preview
output$brand_preview <- renderUI({
req(input$brand_primary, input$brand_secondary, input$brand_accent)
gradient_style <- if(input$use_gradients) {
paste0("background: linear-gradient(135deg, ", input$brand_primary, " 0%, ",
input$brand_secondary, " 100%);")
} else {
paste0("background: ", input$brand_primary, ";")
}
border_radius <- if(input$rounded_corners) "border-radius: 12px;" else "border-radius: 4px;"
div(
# Header with brand
div(
style = paste0(gradient_style, " color: white; padding: 20px; ", border_radius),
div(class = "d-flex justify-content-between align-items-center",
div(
h2(input$company_name, style = paste0("font-family: '", input$brand_font, "', sans-serif; margin: 0; font-weight: 700;")),
p(input$tagline, style = "margin: 0; opacity: 0.9; font-size: 1.1rem;")
),
div(
div(
style = paste0("width: 50px; height: 50px; background: rgba(255,255,255,0.2); ",
border_radius, " display: flex; align-items: center; justify-content: center;"),
bs_icon("building", size = "1.5em")
)
)
)
),
# Main content area
div(style = "padding: 20px;",
# Navigation
div(class = "nav nav-pills mb-4",
tags$a(class = "nav-link active",
style = paste0("background: ", input$brand_primary, "; border-color: ", input$brand_primary, ";"),
"Dashboard"),
tags$a(class = "nav-link", style = paste0("color: ", input$brand_primary, ";"), "Analytics"),
tags$a(class = "nav-link", style = paste0("color: ", input$brand_primary, ";"), "Reports")
),
# Content cards
div(class = "row",
div(class = "col-md-4",
div(class = "card h-100",
style = paste0("border-color: ", input$brand_primary, "; ", border_radius),
div(class = "card-header",
style = paste0("background: linear-gradient(45deg, ", input$brand_primary, ", ", input$brand_accent, "); color: white;"),
h6(bs_icon("graph-up"), "Revenue", style = "margin: 0;")
),
div(class = "card-body text-center",
h3("$124.5K", style = paste0("color: ", input$brand_primary, "; font-family: '", input$brand_font, "', sans-serif;")),
p("↑ 12% from last month", style = paste0("color: ", input$brand_accent, ";"))
)
)
),
div(class = "col-md-4",
div(class = "card h-100",
style = paste0("border-color: ", input$brand_secondary, "; ", border_radius),
div(class = "card-header",
style = paste0("background: ", input$brand_secondary, "; color: white;"),
h6(bs_icon("people"), "Users", style = "margin: 0;")
),
div(class = "card-body text-center",
h3("2,847", style = paste0("color: ", input$brand_secondary, "; font-family: '", input$brand_font, "', sans-serif;")),
p("↑ 8% from last month", style = paste0("color: ", input$brand_accent, ";"))
)
)
),
div(class = "col-md-4",
div(class = "card h-100",
style = paste0("border-color: ", input$brand_accent, "; ", border_radius),
div(class = "card-header",
style = paste0("background: ", input$brand_accent, "; color: white;"),
h6(bs_icon("star"), "Rating", style = "margin: 0;")
),
div(class = "card-body text-center",
h3("4.8", style = paste0("color: ", input$brand_accent, "; font-family: '", input$brand_font, "', sans-serif;")),
p("Based on 156 reviews", style = "color: #6c757d;")
)
)
)
),
# Sample form with brand styling
div(class = "row mt-4",
div(class = "col-md-6",
h5("Sample Form", style = paste0("color: ", input$brand_primary, "; font-family: '", input$brand_font, "', sans-serif;")),
div(class = "mb-3",
tags$label(class = "form-label", "Email Address:"),
tags$input(type = "email", class = "form-control",
style = paste0("border-color: ", input$brand_primary, "; ",
"focus: border-color: ", input$brand_accent, "; box-shadow: 0 0 0 0.2rem rgba(",
paste(col2rgb(input$brand_primary), collapse = ","), ", 0.25);"))
),
tags$button(type = "button",
class = "btn",
style = paste0("background: ", input$brand_primary, "; border-color: ", input$brand_primary,
"; color: white; ", border_radius),
"Submit")
),
div(class = "col-md-6",
h5("Brand Colors", style = paste0("color: ", input$brand_primary, "; font-family: '", input$brand_font, "', sans-serif;")),
div(class = "d-flex gap-3",
div(
div(style = paste0("width: 60px; height: 60px; background: ", input$brand_primary, "; ", border_radius)),
tags$small("Primary", style = "display: block; text-align: center; margin-top: 5px;")
),
div(
div(style = paste0("width: 60px; height: 60px; background: ", input$brand_secondary, "; ", border_radius)),
tags$small("Secondary", style = "display: block; text-align: center; margin-top: 5px;")
),
div(
div(style = paste0("width: 60px; height: 60px; background: ", input$brand_accent, "; ", border_radius)),
tags$small("Accent", style = "display: block; text-align: center; margin-top: 5px;")
)
)
)
)
)
)
})
# CSS Export
output$css_export <- renderText({
req(input$brand_primary, input$brand_secondary, input$brand_accent)
paste0(
"/* ", input$company_name, " Brand System */\n",
":root {\n",
" /* Primary Brand Colors */\n",
" --brand-primary: ", input$brand_primary, ";\n",
" --brand-secondary: ", input$brand_secondary, ";\n",
" --brand-accent: ", input$brand_accent, ";\n\n",
" /* Typography */\n",
" --brand-font: '", input$brand_font, "', sans-serif;\n\n",
" /* Design Tokens */\n",
" --border-radius: ", if(input$rounded_corners) "12px" else "4px", ";\n",
" --use-gradients: ", tolower(input$use_gradients), ";\n",
"}\n\n",
"/* Apply brand theme to Shiny */\n",
".btn-primary {\n",
" background-color: var(--brand-primary);\n",
" border-color: var(--brand-primary);\n",
"}\n\n",
".navbar-brand, h1, h2, h3 {\n",
" font-family: var(--brand-font);\n",
" color: var(--brand-primary);\n",
"}\n\n",
".card {\n",
" border-radius: var(--border-radius);\n",
" border-color: var(--brand-primary);\n",
"}"
)
})
# Enhanced Export Modal
observeEvent(input$export_brand, {
showModal(modalDialog(
title = div(bs_icon("download"), paste(input$company_name, "Brand Package"),
style = "display: flex; align-items: center; gap: 10px;"),
size = "l",
div(
div(class = "alert alert-success",
bs_icon("check-circle"), " Your professional brand system is ready!"
),
h6("Complete Brand System Code:"),
tags$pre(id = "brand-export-code",
style = "background: #f8f9fa; padding: 15px; border-radius: 5px; border: 1px solid #dee2e6; max-height: 400px; overflow-y: auto; font-size: 0.85rem;",
code(
paste0(
"library(shiny)\n",
"library(bslib)\n\n",
"# ", input$company_name, " Brand Theme\n",
"brand_theme <- bs_theme(\n",
" version = 5,\n",
" primary = '", input$brand_primary, "',\n",
" secondary = '", input$brand_secondary, "',\n",
" success = '", input$brand_accent, "',\n",
" base_font = font_google('", input$brand_font, "'),\n",
" bg = '#ffffff',\n",
" fg = '#212529'\n",
")\n\n",
"# Use in your Shiny app:\n",
"ui <- fluidPage(\n",
" theme = brand_theme,\n",
" titlePanel('", input$company_name, "'),\n",
" # Your content here\n",
")\n\n",
"server <- function(input, output, session) {\n",
" # Your server logic\n",
"}\n\n",
"shinyApp(ui = ui, server = server)\n\n",
"# ===== SEPARATE CSS FILE (optional) =====\n",
"# Save the CSS below as 'brand-styles.css' and include in your app:\n",
"# tags$head(tags$link(rel = 'stylesheet', type = 'text/css', href = 'brand-styles.css'))\n\n",
"/* ", input$company_name, " Brand System - brand-styles.css */\n",
":root {\n",
" /* Primary Brand Colors */\n",
" --brand-primary: ", input$brand_primary, ";\n",
" --brand-secondary: ", input$brand_secondary, ";\n",
" --brand-accent: ", input$brand_accent, ";\n\n",
" /* Typography */\n",
" --brand-font: '", input$brand_font, "', sans-serif;\n\n",
" /* Design Tokens */\n",
" --border-radius: ", if(input$rounded_corners) "12px" else "4px", ";\n",
" --use-gradients: ", tolower(input$use_gradients), ";\n",
"}\n\n",
"/* Apply brand theme to Shiny */\n",
".btn-primary {\n",
" background-color: var(--brand-primary);\n",
" border-color: var(--brand-primary);\n",
"}\n\n",
".navbar-brand, h1, h2, h3 {\n",
" font-family: var(--brand-font);\n",
" color: var(--brand-primary);\n",
"}\n\n",
".card {\n",
" border-radius: var(--border-radius);\n",
" border-color: var(--brand-primary);\n",
"}"
)
)
),
div(class = "mt-3",
p(tags$strong("Package includes:"), br(),
"• Complete R/bslib theme code", br(),
"• CSS variables for web styling", br(),
"• Brand styling rules", br(),
"• Ready-to-use implementation", br(),
tags$small("CSS can be used inline with tags$style() or as separate .css file", class = "text-muted")
)
)
),
footer = tagList(
modalButton("Close"),
tags$button(
type = "button",
class = "btn btn-primary",
id = "copy-brand-btn",
onclick = "
try {
const codeElement = document.querySelector('#brand-export-code code');
const textToCopy = codeElement ? (codeElement.textContent || codeElement.innerText) : '';
if (!textToCopy) {
alert('No code to copy. Please try again.');
return;
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(textToCopy).then(() => {
const btn = document.getElementById('copy-brand-btn');
const originalContent = btn.innerHTML;
btn.innerHTML = '<i class=\"bi bi-check\"></i> Copied!';
btn.classList.remove('btn-primary');
btn.classList.add('btn-success');
setTimeout(() => {
btn.innerHTML = originalContent;
btn.classList.remove('btn-success');
btn.classList.add('btn-primary');
}, 2000);
}).catch(err => {
console.error('Clipboard failed:', err);
alert('Copy failed. Please select and copy manually.');
});
} else {
// Fallback method
const textArea = document.createElement('textarea');
textArea.value = textToCopy;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
const btn = document.getElementById('copy-brand-btn');
const originalContent = btn.innerHTML;
btn.innerHTML = '<i class=\"bi bi-check\"></i> Copied!';
btn.classList.remove('btn-primary');
btn.classList.add('btn-success');
setTimeout(() => {
btn.innerHTML = originalContent;
btn.classList.remove('btn-success');
btn.classList.add('btn-primary');
}, 2000);
} else {
alert('Copy failed. Please select and copy manually.');
}
} catch (err) {
alert('Copy failed. Please select and copy manually.');
}
document.body.removeChild(textArea);
}
} catch (error) {
console.error('Copy operation failed:', error);
alert('Copy failed. Please select and copy manually.');
}
",
div(bs_icon("clipboard"), " Copy to Clipboard")
)
)
))
})
}
shinyApp(ui = ui, server = server)
Dynamic Theme Switching
Allow users to switch themes dynamically:
<- fluidPage(
ui theme = bs_theme(), # Start with default
# Theme selector
selectInput("theme_choice", "Choose Theme:",
choices = list(
"Default" = "default",
"Dark" = "darkly",
"Corporate" = "flatly",
"Modern" = "pulse"
)),
# Rest of your UI
titlePanel("Dynamic Theme Application"),
# ... application content
)
<- function(input, output, session) {
server # Reactive theme switching
observe({
if (input$theme_choice == "default") {
bs_themer() # Default theme
else {
} $setCurrentTheme(
sessionbs_theme(bootswatch = input$theme_choice)
)
}
})
# Your server logic
}
Custom CSS Implementation
For complete design control, implement custom CSS that extends or overrides default styling.
Adding CSS to Shiny Applications
Multiple methods for including custom CSS in your applications:
# Method 1: External CSS file (recommended for large projects)
<- fluidPage(
ui # Include external CSS
includeCSS("www/custom-styles.css"),
titlePanel("Custom Styled Application"),
# Your UI content
)
# Create www/custom-styles.css file:
/* www/custom-styles.css */
.custom-title {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
margin-bottom: 30px;
}
.sidebar-custom {
background-color: #f8f9fa;
border-right: 3px solid #007bff;
padding: 20px;
}
# Method 2: Embedded CSS within the application
<- fluidPage(
ui # Embed CSS directly
$head(
tags$style(HTML("
tags .main-header {
background: linear-gradient(90deg, #4285f4, #34a853);
color: white;
padding: 15px;
margin-bottom: 20px;
border-radius: 8px;
}
.custom-input {
border-left: 4px solid #4285f4;
padding-left: 15px;
}
.data-card {
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border-radius: 8px;
padding: 20px;
margin: 10px 0;
}
"))
),
# Use custom classes
div(class = "main-header",
h1("Professional Analytics Dashboard")
),
div(class = "custom-input",
selectInput("dataset", "Choose Dataset:", choices = c("mtcars", "iris"))
) )
# Method 3: Inline styles for specific elements
<- fluidPage(
ui titlePanel("Inline Styled Application"),
# Inline styling for specific elements
div(style = "background-color: #f0f0f0; padding: 20px; border-radius: 10px;",
h3("Custom Styled Section",
style = "color: #2c3e50; border-bottom: 2px solid #3498db;"),
p("This content has custom inline styling.",
style = "font-size: 16px; line-height: 1.6; color: #34495e;")
) )
Professional Color Schemes
Implement sophisticated color systems that create visual hierarchy and brand consistency:
/* Professional Color System */
:root {
/* Primary Brand Colors */
--primary-blue: #2563eb;
--primary-blue-light: #3b82f6;
--primary-blue-dark: #1d4ed8;
/* Secondary Colors */
--secondary-gray: #6b7280;
--secondary-gray-light: #9ca3af;
--secondary-gray-dark: #4b5563;
/* Semantic Colors */
--success-green: #10b981;
--warning-yellow: #f59e0b;
--error-red: #ef4444;
--info-cyan: #06b6d4;
/* Background Colors */
--bg-primary: #ffffff;
--bg-secondary: #f8fafc;
--bg-accent: #f1f5f9;
/* Text Colors */
--text-primary: #1f2937;
--text-secondary: #6b7280;
--text-muted: #9ca3af;
}
/* Apply color system */
.shiny-output-error {
color: var(--error-red);
background-color: rgba(239, 68, 68, 0.1);
border-left: 4px solid var(--error-red);
padding: 10px;
}
.success-message {
color: var(--success-green);
background-color: rgba(16, 185, 129, 0.1);
border-left: 4px solid var(--success-green);
padding: 10px;
}
Typography Enhancement
Create professional typography systems that improve readability and visual hierarchy:
/* Custom Typography System */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
font-weight: 400;
line-height: 1.6;
color: var(--text-primary);
}
/* Heading Hierarchy */
, h2, h3, h4, h5, h6 {
h1font-weight: 600;
line-height: 1.3;
margin-bottom: 1rem;
}
font-size: 2.5rem; color: var(--text-primary); }
h1 { font-size: 2rem; color: var(--text-primary); }
h2 { font-size: 1.5rem; color: var(--text-secondary); }
h3 {
/* Interactive Elements */
.btn {
font-weight: 500;
letter-spacing: 0.025em;
transition: all 0.2s ease;
}
/* Data Display */
.table {
font-size: 0.9rem;
line-height: 1.5;
}
.table th {
font-weight: 600;
color: var(--text-primary);
border-bottom: 2px solid var(--primary-blue);
}
Component-Specific Styling
Enhance individual Shiny components with targeted styling that improves both appearance and functionality.
Custom Input Styling
Transform input controls into polished, branded components:
/* Modern Input Styling */
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
font-weight: 500;
color: var(--text-primary);
margin-bottom: 0.5rem;
display: block;
}
.form-control, .form-select {
border: 2px solid #e2e8f0;
border-radius: 8px;
padding: 12px 16px;
font-size: 14px;
transition: all 0.2s ease;
background-color: #ffffff;
}
.form-control:focus, .form-select:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
outline: none;
}
/* Custom Slider Styling */
.irs-bar {
background: linear-gradient(90deg, var(--primary-blue), var(--primary-blue-light));
}
.irs-handle {
background-color: var(--primary-blue);
border: 3px solid white;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
/* Professional Button System */
.btn-primary {
background: linear-gradient(135deg, var(--primary-blue), var(--primary-blue-light));
border: none;
padding: 12px 24px;
font-weight: 500;
border-radius: 8px;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(37, 99, 235, 0.2);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(37, 99, 235, 0.3);
background: linear-gradient(135deg, var(--primary-blue-dark), var(--primary-blue));
}
.btn-secondary {
background: transparent;
border: 2px solid var(--secondary-gray);
color: var(--secondary-gray-dark);
padding: 10px 22px;
}
.btn-secondary:hover {
background-color: var(--secondary-gray);
color: white;
transform: translateY(-1px);
}
/* Icon Buttons */
.btn-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: var(--bg-accent);
color: var(--text-secondary);
transition: all 0.2s ease;
}
.btn-icon:hover {
background: var(--primary-blue);
color: white;
}
Output Enhancement Styling
Make your data displays more engaging and professional:
/* Enhanced Plot Styling */
.shiny-plot-output {
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07);
background: white;
padding: 20px;
margin: 20px 0;
}
/* Professional Table Styling */
.shiny-table-output table {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.shiny-table-output th {
background: linear-gradient(135deg, var(--primary-blue), var(--primary-blue-light));
color: white;
font-weight: 600;
padding: 15px;
border: none;
}
.shiny-table-output td {
padding: 12px 15px;
border-bottom: 1px solid #f1f5f9;
}
.shiny-table-output tr:hover {
background-color: var(--bg-accent);
}
/* Text Output Enhancement */
.shiny-text-output {
background: var(--bg-secondary);
border-left: 4px solid var(--primary-blue);
padding: 15px 20px;
border-radius: 0 8px 8px 0;
font-family: 'Inter', sans-serif;
font-weight: 500;
color: var(--text-primary);
}
CSS Component Styling Playground
Master advanced CSS techniques for Shiny components:
- Experiment with selectors - Target specific elements with CSS precision
- Apply visual effects - Add shadows, gradients, and animations
- Test responsive behavior - See how styles adapt across screen sizes
- Optimize performance - Learn efficient CSS patterns
- Export production code - Get clean, reusable CSS for your projects
Key Learning: Mastering CSS specificity and component targeting gives you complete control over every visual element in your Shiny applications.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| layout: vertical
#| viewerHeight: 1500
library(shiny)
library(bslib)
library(bsicons)
library(colourpicker)
ui <- fluidPage(
theme = bs_theme(version = 5, bootswatch = "cosmo"),
titlePanel(
div(
bs_icon("tools", size = "1.2em"),
"CSS Component Styling Playground",
style = "display: flex; align-items: center; gap: 10px;"
)
),
fluidRow(
# Style Controls
column(4,
div(class = "card",
div(class = "card-header",
h6(bs_icon("sliders"), "Style Controls", style = "margin: 0;")
),
div(class = "card-body",
# Component Selection
radioButtons("target_component", "Target Component:",
choices = list(
"Buttons" = "buttons",
"Input Controls" = "inputs",
"Data Tables" = "tables",
"Cards" = "cards",
"Plots" = "plots"
),
selected = "buttons"),
hr(),
# Visual Effects
h6("Visual Effects:"),
checkboxInput("add_shadow", "Drop Shadow", FALSE),
checkboxInput("add_gradient", "Gradient Background", FALSE),
checkboxInput("add_animation", "Hover Animation", FALSE),
checkboxInput("rounded_corners", "Rounded Corners", TRUE),
conditionalPanel(
condition = "input.add_shadow",
sliderInput("shadow_intensity", "Shadow Intensity:",
min = 1, max = 10, value = 3)
),
conditionalPanel(
condition = "input.add_gradient",
colourInput("gradient_start", "Gradient Start:", "#6366f1"),
colourInput("gradient_end", "Gradient End:", "#8b5cf6")
),
hr(),
# Colors
h6("Colors:"),
colourInput("primary_color", "Primary:", "#0d6efd"),
colourInput("text_color", "Text:", "#212529"),
colourInput("border_color", "Border:", "#dee2e6"),
hr(),
# Spacing
h6("Spacing & Size:"),
sliderInput("padding", "Padding:", min = 5, max = 30, value = 12),
sliderInput("margin", "Margin:", min = 0, max = 20, value = 5),
sliderInput("border_width", "Border Width:", min = 0, max = 5, value = 1),
hr(),
# Export
actionButton("export_css", "Export CSS Code",
class = "btn-primary w-100")
)
)
),
# Component Preview
column(8,
div(class = "card",
div(class = "card-header",
h6(bs_icon("eye"), "Component Preview", style = "margin: 0;")
),
div(class = "card-body",
uiOutput("component_preview")
)
)
)
),
# Generated CSS Display
fluidRow(
column(12,
div(class = "card mt-3",
div(class = "card-header",
h6(bs_icon("code-square"), "Generated CSS", style = "margin: 0;")
),
div(class = "card-body",
verbatimTextOutput("generated_css")
)
)
)
)
)
server <- function(input, output, session) {
# Generate CSS based on inputs
generated_styles <- reactive({
shadow <- if(input$add_shadow) {
intensity <- input$shadow_intensity
paste0("box-shadow: 0 ", ceiling(intensity/2), "px ", intensity * 2, "px rgba(0,0,0,", 0.1 + (intensity * 0.05), ");")
} else ""
gradient <- if(input$add_gradient) {
paste0("background: linear-gradient(135deg, ", input$gradient_start, " 0%, ", input$gradient_end, " 100%);")
} else {
paste0("background-color: ", input$primary_color, ";")
}
animation <- if(input$add_animation) {
"transition: all 0.3s ease; transform: scale(1);"
} else ""
hover_animation <- if(input$add_animation) {
"transform: scale(1.05); box-shadow: 0 8px 25px rgba(0,0,0,0.15);"
} else ""
border_radius <- if(input$rounded_corners) "8px" else "0px"
list(
base = paste0(
gradient, " ",
"color: ", input$text_color, "; ",
"border: ", input$border_width, "px solid ", input$border_color, "; ",
"border-radius: ", border_radius, "; ",
"padding: ", input$padding, "px; ",
"margin: ", input$margin, "px; ",
shadow, " ",
animation
),
hover = hover_animation
)
})
# Component Preview
output$component_preview <- renderUI({
styles <- generated_styles()
# Apply styles dynamically
component_id <- paste0("styled_", input$target_component)
# Inject CSS
css_rules <- paste0(
"#", component_id, " .styled-element { ", styles$base, " }",
if(input$add_animation) {
paste0("#", component_id, " .styled-element:hover { ", styles$hover, " }")
} else ""
)
content <- switch(input$target_component,
"buttons" = div(id = component_id,
h5("Button Styling Preview"),
div(class = "d-flex gap-2 flex-wrap",
tags$button(type = "button", class = "btn styled-element", "Primary Button"),
tags$button(type = "button", class = "btn styled-element", "Secondary Button"),
tags$button(type = "button", class = "btn styled-element",
bs_icon("download"), " Download")
)
),
"inputs" = div(id = component_id,
h5("Input Control Styling Preview"),
div(class = "row",
div(class = "col-md-6",
tags$label(class = "form-label", "Text Input:"),
tags$input(type = "text", class = "form-control styled-element",
placeholder = "Enter text here")
),
div(class = "col-md-6",
tags$label(class = "form-label", "Select Input:"),
tags$select(class = "form-select styled-element",
tags$option("Option 1"),
tags$option("Option 2"),
tags$option("Option 3")
)
)
),
div(class = "row mt-3",
div(class = "col-md-12",
tags$label(class = "form-label", "Textarea:"),
tags$textarea(class = "form-control styled-element",
placeholder = "Enter your message here...",
rows = "3")
)
)
),
"tables" = div(id = component_id,
h5("Table Styling Preview"),
tags$table(class = "table styled-element",
tags$thead(
tags$tr(
tags$th("Name"),
tags$th("Role"),
tags$th("Department"),
tags$th("Status")
)
),
tags$tbody(
tags$tr(
tags$td("John Doe"),
tags$td("Manager"),
tags$td("Engineering"),
tags$td(tags$span(class = "badge bg-success", "Active"))
),
tags$tr(
tags$td("Jane Smith"),
tags$td("Developer"),
tags$td("Engineering"),
tags$td(tags$span(class = "badge bg-success", "Active"))
),
tags$tr(
tags$td("Bob Wilson"),
tags$td("Designer"),
tags$td("Creative"),
tags$td(tags$span(class = "badge bg-warning", "Pending"))
)
)
)
),
"cards" = div(id = component_id,
h5("Card Styling Preview"),
div(class = "row",
div(class = "col-md-6",
div(class = "card styled-element",
div(class = "card-header",
h6(bs_icon("graph-up"), "Analytics", style = "margin: 0;")
),
div(class = "card-body",
h4("$24,580", style = "color: #28a745; margin-bottom: 10px;"),
p("Total revenue this month", style = "margin: 0; color: #6c757d;")
)
)
),
div(class = "col-md-6",
div(class = "card styled-element",
div(class = "card-header",
h6(bs_icon("people"), "Users", style = "margin: 0;")
),
div(class = "card-body",
h4("1,247", style = "color: #17a2b8; margin-bottom: 10px;"),
p("Active users today", style = "margin: 0; color: #6c757d;")
)
)
)
)
),
"plots" = div(id = component_id,
h5("Plot Container Styling Preview"),
div(class = "styled-element",
style = "height: 200px; display: flex; align-items: center; justify-content: center; background: #f8f9fa;",
div(style = "text-align: center;",
bs_icon("bar-chart", size = "3em", class = "text-muted"),
h6("Plot Output Area", style = "margin-top: 10px; color: #6c757d;"),
p("Your styled plot would appear here", style = "margin: 0; font-size: 0.9rem; color: #6c757d;")
)
)
)
)
# Return content with injected styles
tagList(
tags$style(HTML(css_rules)),
content
)
})
# Generated CSS output
output$generated_css <- renderText({
styles <- generated_styles()
component_class <- switch(input$target_component,
"buttons" = ".btn",
"inputs" = ".form-control, .form-select",
"tables" = ".table",
"cards" = ".card",
"plots" = ".shiny-plot-output"
)
base_css <- paste0(
"/* Styled ", tools::toTitleCase(input$target_component), " */\n",
component_class, " {\n",
" ", gsub("; ", ";\n ", styles$base), "\n",
"}"
)
hover_css <- if(input$add_animation && styles$hover != "") {
paste0(
"\n\n", component_class, ":hover {\n",
" ", gsub("; ", ";\n ", styles$hover), "\n",
"}"
)
} else ""
paste0(base_css, hover_css)
})
# Export CSS functionality with modal
observeEvent(input$export_css, {
styles <- generated_styles()
component_class <- switch(input$target_component,
"buttons" = ".btn",
"inputs" = ".form-control, .form-select",
"tables" = ".table",
"cards" = ".card",
"plots" = ".shiny-plot-output"
)
css_code <- paste0(
"/* Custom ", tools::toTitleCase(input$target_component), " Styles */\n",
"/* Generated by Component Styling Playground */\n\n",
component_class, " {\n",
" ", gsub("; ", ";\n ", styles$base), "\n",
"}",
if(input$add_animation && styles$hover != "") {
paste0(
"\n\n", component_class, ":hover {\n",
" ", gsub("; ", ";\n ", styles$hover), "\n",
"}"
)
} else ""
)
showModal(modalDialog(
title = div(bs_icon("download"), paste("Export", tools::toTitleCase(input$target_component), "Styles"),
style = "display: flex; align-items: center; gap: 10px;"),
size = "l",
div(
div(class = "alert alert-success",
bs_icon("check-circle"), " Your custom component styles are ready!"
),
h6("CSS Code for Your Components:"),
tags$pre(id = "css-export-code",
style = "background: #f8f9fa; padding: 15px; border-radius: 5px; border: 1px solid #dee2e6; max-height: 400px; overflow-y: auto; font-size: 0.85rem;",
code(css_code)
),
div(class = "mt-3",
p(tags$strong("Usage Instructions:"), br(),
"• Add this CSS to your Shiny app using ", tags$code("tags$style()"), br(),
"• Or save as a .css file and include with ", tags$code("tags$link()"), br(),
"• Customize the selectors as needed for your specific use case", br(),
tags$small("The CSS targets ", component_class, " elements with the styling you configured", class = "text-muted")
)
)
),
footer = tagList(
modalButton("Close"),
tags$button(
type = "button",
class = "btn btn-primary",
id = "copy-css-btn",
onclick = "
try {
const codeElement = document.querySelector('#css-export-code code');
const textToCopy = codeElement ? (codeElement.textContent || codeElement.innerText) : '';
if (!textToCopy) {
alert('No CSS to copy. Please try again.');
return;
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(textToCopy).then(() => {
const btn = document.getElementById('copy-css-btn');
const originalContent = btn.innerHTML;
btn.innerHTML = '<i class=\"bi bi-check\"></i> Copied!';
btn.classList.remove('btn-primary');
btn.classList.add('btn-success');
setTimeout(() => {
btn.innerHTML = originalContent;
btn.classList.remove('btn-success');
btn.classList.add('btn-primary');
}, 2000);
}).catch(err => {
console.error('Clipboard failed:', err);
alert('Copy failed. Please select and copy manually.');
});
} else {
// Fallback method
const textArea = document.createElement('textarea');
textArea.value = textToCopy;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
const btn = document.getElementById('copy-css-btn');
const originalContent = btn.innerHTML;
btn.innerHTML = '<i class=\"bi bi-check\"></i> Copied!';
btn.classList.remove('btn-primary');
btn.classList.add('btn-success');
setTimeout(() => {
btn.innerHTML = originalContent;
btn.classList.remove('btn-success');
btn.classList.add('btn-primary');
}, 2000);
} else {
alert('Copy failed. Please select and copy manually.');
}
} catch (err) {
alert('Copy failed. Please select and copy manually.');
}
document.body.removeChild(textArea);
}
} catch (error) {
console.error('Copy operation failed:', error);
alert('Copy failed. Please select and copy manually.');
}
",
div(bs_icon("clipboard"), " Copy CSS Code")
),
tags$a(
class = "btn btn-success",
download = paste0("shiny_", input$target_component, "_styles.css"),
href = "#",
onclick = "
const content = document.querySelector('#css-export-code code').textContent;
const blob = new Blob([content], { type: 'text/css' });
const url = window.URL.createObjectURL(blob);
this.href = url;
",
div(bs_icon("download"), " Download CSS File")
)
)
))
})
}
shinyApp(ui = ui, server = server)
Advanced Layout and Animation
Create engaging user experiences with sophisticated layouts, transitions, and micro-interactions.
Card-Based Layouts
Implement modern card-based designs that organize content effectively:
# R code for card layout structure
<- fluidPage(
ui theme = bs_theme(bootswatch = "flatly"),
div(class = "dashboard-container",
div(class = "dashboard-header",
h1("Analytics Dashboard", class = "dashboard-title"),
p("Real-time business intelligence", class = "dashboard-subtitle")
),
div(class = "card-grid",
# KPI Cards
div(class = "kpi-card",
div(class = "kpi-icon", icon("chart-line")),
div(class = "kpi-content",
h3("$124,567", class = "kpi-value"),
p("Total Revenue", class = "kpi-label")
)
),
div(class = "kpi-card",
div(class = "kpi-icon", icon("users")),
div(class = "kpi-content",
h3("2,847", class = "kpi-value"),
p("Active Users", class = "kpi-label")
)
),
# Chart Card
div(class = "chart-card",
div(class = "card-header",
h4("Performance Trends")
),div(class = "card-body",
plotOutput("trend_chart")
)
)
)
) )
/* Card-based Layout CSS */
.dashboard-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.dashboard-header {
text-align: center;
margin-bottom: 40px;
}
.dashboard-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 8px;
}
.dashboard-subtitle {
font-size: 1.1rem;
color: var(--text-secondary);
margin: 0;
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.kpi-card {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06);
display: flex;
align-items: center;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.kpi-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.12);
}
.kpi-icon {
width: 60px;
height: 60px;
border-radius: 12px;
background: linear-gradient(135deg, var(--primary-blue), var(--primary-blue-light));
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
margin-right: 20px;
}
.kpi-value {
font-size: 1.8rem;
font-weight: 700;
color: var(--text-primary);
margin: 0;
}
.kpi-label {
color: var(--text-secondary);
margin: 4px 0 0 0;
font-size: 0.9rem;
}
.chart-card {
grid-column: 1 / -1;
background: white;
border-radius: 12px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.card-header {
padding: 20px 24px;
border-bottom: 1px solid #f1f5f9;
background: var(--bg-secondary);
}
.card-body {
padding: 24px;
}
Smooth Animations and Transitions
Add polished animations that enhance user experience:
/* Animation System */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(30px);
}to {
opacity: 1;
transform: translateX(0);
}
}
/* Apply animations to components */
.shiny-output-error, .shiny-output-error-validation {
animation: fadeInUp 0.3s ease;
}
.sidebar-panel {
animation: slideInRight 0.4s ease;
}
/* Loading animations */
.loading-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(37, 99, 235, 0.3);
border-radius: 50%;
border-top-color: var(--primary-blue);
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Hover effects */
.interactive-element {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.interactive-element:hover {
transform: scale(1.02);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
}
Brand Integration and Custom Identity
Transform your Shiny applications into branded experiences that reflect your organization’s visual identity.
Logo and Brand Asset Integration
Seamlessly incorporate brand elements into your application:
# Brand-integrated header
<- fluidPage(
ui # Custom branded header
div(class = "brand-header",
div(class = "brand-container",
div(class = "brand-logo",
img(src = "logo.png", alt = "Company Logo", height = "40px")
),div(class = "brand-text",
h1("Analytics Platform", class = "brand-title"),
p("Powered by Data Science", class = "brand-tagline")
),div(class = "brand-actions",
actionButton("export", "Export Report", class = "btn-brand"),
actionButton("settings", "Settings", class = "btn-outline-brand")
)
)
),
# Rest of application
div(class = "app-content",
# Your application content
) )
# Branded navigation system
<- navbarPage(
ui title = div(
img(src = "logo-white.png", height = "30px", style = "margin-right: 10px;"),
"Corporate Dashboard"
),
theme = bs_theme(
primary = "#1E3A8A", # Corporate blue
navbar_bg = "#1E3A8A",
navbar_fg = "#FFFFFF"
),
tabPanel("Overview",
# Overview content
),tabPanel("Analytics",
# Analytics content
),tabPanel("Reports",
# Reports content
) )
/* Brand Integration CSS */
.brand-header {
background: linear-gradient(135deg, #1E3A8A 0%, #3B82F6 100%);
color: white;
padding: 16px 0;
margin-bottom: 30px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.brand-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
}
.brand-logo {
display: flex;
align-items: center;
}
.brand-text {
flex: 1;
margin-left: 16px;
}
.brand-title {
font-size: 1.5rem;
font-weight: 600;
margin: 0;
color: white;
}
.brand-tagline {
font-size: 0.9rem;
margin: 0;
opacity: 0.9;
}
.brand-actions {
display: flex;
gap: 12px;
}
.btn-brand {
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
backdrop-filter: blur(10px);
}
.btn-brand:hover {
background: rgba(255, 255, 255, 0.3);
color: white;
}
Color System Implementation
Create comprehensive color systems that maintain brand consistency:
/* Comprehensive Brand Color System */
:root {
/* Primary Brand Colors */
--brand-primary: #1E3A8A;
--brand-primary-light: #3B82F6;
--brand-primary-dark: #1E40AF;
/* Secondary Brand Colors */
--brand-secondary: #059669;
--brand-secondary-light: #10B981;
--brand-secondary-dark: #047857;
/* Accent Colors */
--brand-accent-1: #F59E0B;
--brand-accent-2: #EF4444;
--brand-accent-3: #8B5CF6;
/* Neutral Brand Colors */
--brand-gray-50: #F9FAFB;
--brand-gray-100: #F3F4F6;
--brand-gray-200: #E5E7EB;
--brand-gray-300: #D1D5DB;
--brand-gray-400: #9CA3AF;
--brand-gray-500: #6B7280;
--brand-gray-600: #4B5563;
--brand-gray-700: #374151;
--brand-gray-800: #1F2937;
--brand-gray-900: #111827;
}
/* Apply brand colors to Shiny components */
.btn-primary {
background-color: var(--brand-primary);
border-color: var(--brand-primary);
}
.btn-primary:hover {
background-color: var(--brand-primary-dark);
border-color: var(--brand-primary-dark);
}
.form-control:focus {
border-color: var(--brand-primary-light);
box-shadow: 0 0 0 0.2rem rgba(59, 130, 246, 0.25);
}
/* Brand-specific component colors */
.progress-bar {
background-color: var(--brand-primary);
}
.nav-pills .nav-link.active {
background-color: var(--brand-primary);
}
.text-primary { color: var(--brand-primary) !important; }
.text-secondary { color: var(--brand-secondary) !important; }
.bg-primary { background-color: var(--brand-primary) !important; }
.bg-secondary { background-color: var(--brand-secondary) !important; }
Typography and Brand Voice
Implement consistent typography that reflects your brand personality:
/* Brand Typography System */
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&family=Open+Sans:wght@400;500;600&display=swap');
/* Brand Typography Variables */
:root {
--brand-font-heading: 'Poppins', sans-serif;
--brand-font-body: 'Open Sans', sans-serif;
--brand-font-mono: 'JetBrains Mono', monospace;
}
/* Apply brand typography */
, .shiny-input-container label {
bodyfont-family: var(--brand-font-body);
font-weight: 400;
line-height: 1.6;
}
, h2, h3, h4, h5, h6, .brand-heading {
h1font-family: var(--brand-font-heading);
font-weight: 600;
line-height: 1.3;
color: var(--brand-gray-800);
}
/* Specific heading sizes */
font-size: 2.25rem; font-weight: 700; }
h1 { font-size: 1.875rem; font-weight: 600; }
h2 { font-size: 1.5rem; font-weight: 600; }
h3 { font-size: 1.25rem; font-weight: 500; }
h4 {
/* Brand-specific text classes */
.brand-text-large {
font-size: 1.125rem;
font-weight: 500;
color: var(--brand-gray-700);
}
.brand-text-small {
font-size: 0.875rem;
color: var(--brand-gray-600);
}
.brand-text-xs {
font-size: 0.75rem;
color: var(--brand-gray-500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
Responsive Design Implementation
Ensure your styled applications work beautifully across all devices and screen sizes.
Mobile-First Responsive Patterns
Implement responsive design that prioritizes mobile experience:
/* Mobile-First Responsive Design */
/* Base mobile styles */
.container-fluid {
padding-left: 15px;
padding-right: 15px;
}
.sidebar-panel {
margin-bottom: 20px;
}
/* Responsive navigation */
.navbar-brand {
font-size: 1.1rem;
}
/* Responsive cards */
.kpi-card {
flex-direction: column;
text-align: center;
padding: 20px;
}
.kpi-icon {
margin-right: 0;
margin-bottom: 15px;
}
/* Responsive tables */
.table-responsive {
font-size: 0.875rem;
}
/* Tablet styles */
@media (min-width: 768px) {
.container-fluid {
padding-left: 30px;
padding-right: 30px;
}
.kpi-card {
flex-direction: row;
text-align: left;
padding: 24px;
}
.kpi-icon {
margin-right: 20px;
margin-bottom: 0;
}
.sidebar-panel {
margin-bottom: 0;
}
}
/* Desktop styles */
@media (min-width: 992px) {
.brand-title {
font-size: 1.75rem;
}
.dashboard-container {
padding: 30px;
}
.card-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* Large desktop styles */
@media (min-width: 1200px) {
.container-fluid {
max-width: 1200px;
margin: 0 auto;
}
.card-grid {
grid-template-columns: repeat(4, 1fr);
} }
Responsive Component Patterns
Create components that adapt gracefully to different screen sizes:
# Responsive Shiny layout patterns
<- fluidPage(
ui theme = bs_theme(bootswatch = "flatly"),
# Responsive header
div(class = "responsive-header",
div(class = "header-content",
div(class = "header-brand",
img(src = "logo.png", class = "brand-logo-responsive"),
h1("Dashboard", class = "header-title-responsive")
),div(class = "header-actions d-none d-md-flex",
actionButton("export", "Export", class = "btn btn-outline-primary"),
actionButton("settings", icon("cog"), class = "btn btn-primary")
)
)
),
# Responsive main layout
div(class = "responsive-layout",
# Sidebar that becomes top section on mobile
div(class = "responsive-sidebar",
div(class = "sidebar-content",
h4("Filters"),
selectInput("dataset", "Dataset:", choices = c("A", "B", "C")),
sliderInput("range", "Range:", 1, 100, c(20, 80)),
# Mobile-only action buttons
div(class = "d-md-none mt-3",
actionButton("apply", "Apply Filters", class = "btn btn-primary w-100")
)
)
),
# Main content area
div(class = "responsive-main",
div(class = "content-grid",
# KPI cards that stack on mobile
div(class = "kpi-section",
div(class = "kpi-card-responsive",
h3("$45,231", class = "kpi-value"),
p("Revenue", class = "kpi-label")
),div(class = "kpi-card-responsive",
h3("1,234", class = "kpi-value"),
p("Users", class = "kpi-label")
)
),
# Chart that scales appropriately
div(class = "chart-section",
plotOutput("main_chart", height = "400px")
)
)
)
) )
/* Responsive component CSS */
.responsive-header {
background: var(--brand-primary);
color: white;
padding: 1rem;
margin-bottom: 1.5rem;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
}
.brand-logo-responsive {
height: 32px;
margin-right: 12px;
}
.header-title-responsive {
font-size: 1.25rem;
margin: 0;
display: inline-block;
}
.responsive-layout {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.responsive-sidebar {
background: var(--brand-gray-50);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.kpi-section {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
margin-bottom: 1.5rem;
}
.kpi-card-responsive {
background: white;
padding: 1.5rem;
border-radius: 8px;
text-align: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Tablet and up */
@media (min-width: 768px) {
.responsive-layout {
display: grid;
grid-template-columns: 300px 1fr;
gap: 2rem;
padding: 0 2rem;
}
.responsive-sidebar {
margin-bottom: 0;
}
.kpi-section {
grid-template-columns: repeat(2, 1fr);
}
.header-title-responsive {
font-size: 1.5rem;
}
.brand-logo-responsive {
height: 40px;
}
}
/* Desktop and up */
@media (min-width: 992px) {
.kpi-section {
grid-template-columns: repeat(3, 1fr);
}
.responsive-layout {
grid-template-columns: 350px 1fr;
} }
Performance and Optimization
Ensure your styled applications maintain excellent performance while delivering beautiful user experiences.
CSS Performance Best Practices
Optimize CSS delivery and rendering performance:
/* Performance-optimized CSS structure */
/* Critical above-the-fold styles */
.critical-styles {
/* Only include styles for immediately visible content */
}
/* Use efficient selectors */
.specific-class { /* Good - class selector */ }
#specific-id { /* Good - ID selector */ }
.class { /* Avoid - compound selectors when possible */ }
div* { /* Avoid - universal selector */ }
/* Minimize expensive properties */
.performant-box {
/* Use transform instead of changing layout properties */
transform: translateY(-2px);
/* Use will-change for elements that will be animated */
will-change: transform;
}
/* Efficient animations */
@keyframes slideIn {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
.animated-element {
/* Use transform and opacity for smooth animations */
transition: transform 0.3s ease, opacity 0.3s ease;
}
/* Avoid layout-triggering properties in animations */
.avoid-layout-animation {
/* Avoid animating: width, height, padding, margin, top, left */
/* Prefer: transform, opacity, filter */
}
Conditional Styling Loading
Load styles efficiently based on application needs:
# Conditional CSS loading in Shiny
<- fluidPage(
ui # Load critical CSS immediately
$head(
tags# Critical styles inline for fastest loading
$style(HTML("
tags body { font-family: system-ui, sans-serif; }
.loading { opacity: 0; }
")),
# Load non-critical CSS asynchronously
$link(rel = "preload", href = "css/dashboard.css", as = "style",
tagsonload = "this.onload=null;this.rel='stylesheet'"),
# Fallback for browsers that don't support preload
$noscript(
tags$link(rel = "stylesheet", href = "css/dashboard.css")
tags
),
# Load theme-specific CSS conditionally
conditionalPanel(
condition = "input.theme_selection == 'dark'",
$link(rel = "stylesheet", href = "css/dark-theme.css")
tags
)
),
# Your application content
titlePanel("Performance-Optimized App")
)
Common Issues and Solutions
Issue 1: Styles Not Applying or Being Overridden
Problem: Custom CSS styles don’t appear or are inconsistently applied.
Solution:
Debug CSS specificity and loading order:
/* Check CSS specificity - more specific selectors win */
/* Low specificity */
.btn { background: blue; }
/* Higher specificity */
.shiny-input-container .btn { background: red; }
/* Highest specificity */
.container .shiny-input-container .btn { background: green; }
body
/* Use !important sparingly and strategically */
.critical-override {
background: purple !important; /* Only when necessary */
}
Debugging Steps:
- Use browser developer tools to inspect elements
- Check if styles are loading (Network tab)
- Verify CSS selector specificity
- Ensure proper file paths for external CSS
- Check for syntax errors in CSS
Issue 2: Responsive Design Breaking on Mobile
Problem: Application doesn’t display properly on mobile devices or tablets.
Solution:
Implement proper responsive patterns:
/* Fix common responsive issues */
/* Ensure proper viewport meta tag in head */
/* Add this to your Shiny app: */
/* tags$meta(name = "viewport", content = "width=device-width, initial-scale=1") */
/* Fix horizontal scrolling issues */
, html {
bodyoverflow-x: hidden;
max-width: 100%;
}
/* Make tables responsive */
.table-responsive {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Fix input sizing on mobile */
.form-control {
max-width: 100%;
box-sizing: border-box;
}
/* Stack elements on small screens */
@media (max-width: 767px) {
.row > div {
margin-bottom: 15px;
}
.btn {
width: 100%;
margin-bottom: 10px;
} }
Issue 3: Theme Conflicts with Shiny Components
Problem: Custom themes conflict with built-in Shiny styling or third-party packages.
Solution:
Use proper CSS scoping and namespace management:
/* Scope custom styles to avoid conflicts */
.custom-app-container {
/* Your custom styles only apply within this container */
}
.custom-app-container .btn-primary {
/* Override only within your app scope */
background: var(--brand-primary);
}
/* Use CSS custom properties for easy theme switching */
:root {
--app-primary: #3498db;
--app-secondary: #2ecc71;
}
[data-theme="dark"] {
--app-primary: #5dade2;
--app-secondary: #58d68d;
}
/* Specific overrides for problematic components */
.dataTables_wrapper .dataTables_filter input {
/* Fix DataTables input styling conflicts */
border: 1px solid #ddd !important;
border-radius: 4px !important;
}
/* Reset problematic inherited styles */
.shiny-plot-output img {
/* Reset any unwanted image styles */
max-width: none;
height: auto;
}
- Load critical CSS inline for immediate rendering
- Use CSS custom properties for dynamic theming
- Minimize CSS file size by removing unused styles
- Optimize selector specificity to avoid conflicts
- Test responsive design on actual devices, not just browser dev tools
Common Questions About Shiny Styling
Use bslib themes when you need quick, professional results with minimal custom development. They’re perfect for business applications, dashboards, and when you want to leverage Bootstrap’s ecosystem. Choose custom CSS when you need complete design control, specific brand requirements, or unique visual experiences that pre-built themes can’t accommodate. Many successful projects combine both - starting with a bslib theme and adding custom CSS for specific components.
Create a centralized design system with shared CSS files, color variables, and component definitions. Use CSS custom properties (variables) for brand colors, typography, and spacing that can be easily updated across all applications. Consider creating a custom R package that includes your styling assets, making it easy to maintain consistency and push updates across your application portfolio.
Test theme compatibility early with packages like DT, plotly, leaflet, and shinydashboard. Many extension packages include their own CSS that may conflict with custom themes. Use CSS specificity strategically to override extension package styles when needed, and consider using CSS scoping to isolate your custom styles. Document any package-specific styling requirements for your team.
For development and small applications, embedded CSS using tags$style()
is convenient and keeps everything in one file. For production applications, use external CSS files that can be cached by browsers, versioned separately, and shared across applications. Consider a hybrid approach: critical above-the-fold styles embedded for fast loading, with detailed styling in external files.
Follow WCAG guidelines by ensuring sufficient color contrast (4.5:1 for normal text, 3:1 for large text), providing focus indicators for interactive elements, and using semantic HTML structure. Test your color schemes with accessibility tools, ensure your application works with screen readers, and avoid relying solely on color to convey information. Modern CSS frameworks like Bootstrap include many accessibility features by default.
Test Your Understanding
You want to change the background color of all primary buttons in your Shiny app, but your custom CSS isn’t working. The buttons still show the default Bootstrap blue color. Which approach will most effectively override the default styling?
.btn { background-color: red; }
button.btn-primary { background-color: red !important; }
.btn-primary { background-color: red !important; }
.shiny-input-container .btn-primary { background-color: red; }
- Consider CSS specificity rules and how Bootstrap styles are structured
- Think about which selectors are more specific than others
- Consider whether
!important
is necessary or if higher specificity is better
D) .shiny-input-container .btn-primary { background-color: red; }
This approach works best because:
Why D is correct: - Higher specificity than the default Bootstrap selector - No !important needed - cleaner, more maintainable CSS - Targets the right context where Shiny buttons actually appear - Follows CSS best practices for specificity-based overrides
Why other options are less optimal: - A: Too generic and low specificity - B: Wrong element selector (buttons aren’t always <button>
tags in Shiny) - C: Same specificity as Bootstrap default, may not consistently override
The key is understanding that Bootstrap styles have specific selectors, so you need equal or higher specificity to override them reliably.
Complete this CSS to create a responsive card grid that shows 1 card per row on mobile, 2 on tablet, and 3 on desktop:
.card-grid {
display: grid;
gap: 20px;
grid-template-columns: _______;
}
@media (min-width: 768px) {
.card-grid {
grid-template-columns: _______;
}
}
@media (min-width: 992px) {
.card-grid {
grid-template-columns: _______;
} }
- Think about mobile-first responsive design principles
- Consider how CSS Grid
fr
units work for equal-width columns - Remember that media queries build upon previous breakpoints
.card-grid {
display: grid;
gap: 20px;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 992px) {
.card-grid {
grid-template-columns: repeat(3, 1fr);
} }
Mobile-first approach explained: - Base styles: 1 column (1fr
) for mobile devices - Tablet breakpoint (768px+): 2 equal columns (repeat(2, 1fr)
) - Desktop breakpoint (992px+): 3 equal columns (repeat(3, 1fr)
)
This approach ensures content is readable on small screens first, then enhanced for larger screens.
You’re building a Shiny application for a company with an existing brand identity. They have specific colors, fonts, and a logo. What’s the most maintainable approach for integrating their brand across multiple Shiny applications?
- Hard-code brand colors and fonts directly in each application’s CSS
- Create CSS custom properties (variables) for brand elements and use external CSS files
- Use inline styles throughout the Shiny UI code for maximum control
- Modify the Bootstrap source code to include brand colors
- Think about maintainability across multiple applications
- Consider what happens when brand guidelines change
- Think about separation of concerns and code organization
B) Create CSS custom properties (variables) for brand elements and use external CSS files
This approach provides the best maintainability and scalability:
/* brand-system.css */
:root {
--brand-primary: #1E3A8A;
--brand-secondary: #059669;
--brand-font-heading: 'Poppins', sans-serif;
--brand-font-body: 'Open Sans', sans-serif;
}
.btn-primary {
background-color: var(--brand-primary);
}
, h2, h3 {
h1font-family: var(--brand-font-heading);
}
Why this is optimal:
- Centralized brand management: Update variables in one place
- Easy maintenance: Brand changes only require CSS updates
- Consistency: Same brand elements across all applications
- Flexibility: Can override variables for specific contexts
- Performance: External CSS files can be cached by browsers
Why other options are problematic:
- A: Requires updating every application individually
- C: Mixes styling with application logic, hard to maintain
- D: Modifies third-party code, breaks with updates
Conclusion
Mastering Shiny styling and theming transforms your applications from functional tools into professional, branded experiences that users genuinely enjoy using. The techniques you’ve learned - from modern bslib themes and custom CSS implementation to responsive design and brand integration - provide the foundation for creating visually compelling applications that stand out in today’s competitive landscape.
The styling skills you’ve developed extend far beyond simple color changes. You now understand how to create cohesive design systems, implement responsive layouts that work across all devices, and integrate sophisticated branding that reflects organizational identity. These capabilities position you to build applications that not only deliver analytical insights but do so with the polish and professionalism that modern users expect.
Your journey through Shiny’s styling ecosystem prepares you to tackle advanced UI design challenges and create applications that serve as flagship examples of what’s possible when powerful analytics meet exceptional design.
Next Steps
Based on your new styling expertise, here are the recommended paths for advancing your Shiny design skills:
Immediate Next Steps (Complete These First)
- Responsive Design for Shiny Apps - Deep dive into mobile-first design and advanced responsive patterns
- Advanced UI Components and Custom HTML - Create sophisticated custom components that integrate with your styling system
- Practice Exercise: Take an existing Shiny application and implement a complete brand makeover using the techniques from this guide
Building on Your Foundation (Choose Your Path)
For Advanced Design Focus:
- Interactive Features and Dynamic UI
- Building Interactive Dashboards
- JavaScript Integration and Custom JS
For Professional Development:
- Code Organization and Project Structure
- Accessibility and Performance
- Deploying to shinyapps.io Platform
For Business Applications:
Long-term Goals (2-4 Weeks)
- Create a comprehensive design system that can be reused across multiple Shiny applications
- Build a portfolio-worthy application that showcases advanced styling and branding techniques
- Develop custom Shiny components with integrated styling that can be shared with the community
- Master advanced CSS techniques like CSS Grid, Flexbox, and modern animation patterns for Shiny applications
Explore More Articles
Here are more articles from the same category to help you dive deeper into the topic.
Reuse
Citation
@online{kassambara2025,
author = {Kassambara, Alboukadel},
title = {Styling and {Custom} {Themes} in {Shiny:} {Professional}
{Design} {Guide}},
date = {2025-05-23},
url = {https://www.datanovia.com/learn/tools/shiny-apps/ui-design/styling-themes.html},
langid = {en}
}