shiny-bslib-theming
Advanced theming for Shiny apps using bslib and Bootstrap 5. Use when customizing app appearance with bs_theme(), Bootswatch themes, custom colors, typography, brand.yml integration, Bootstrap Sass variables, custom Sass/CSS rules, dark mode and color modes, dynamic theme switching, real-time theming, theme inspection, or making R plots match the app theme with thematic.
What this skill does
# Theming Shiny Apps with bslib
Customize Shiny app appearance using bslib's Bootstrap 5 theming system. From quick Bootswatch themes to advanced Sass customization and dynamic color mode switching.
## Quick Start
**"shiny" preset (recommended starting point):**
```r
page_sidebar(
theme = bs_theme(), # "shiny" preset by default — polished, not plain Bootstrap
...
)
```
**Bootswatch theme (for a different visual style):**
```r
page_sidebar(
theme = bs_theme(preset = "zephyr"), # or "cosmo", "minty", "darkly", etc.
...
)
```
**Custom colors and fonts:**
```r
page_sidebar(
theme = bs_theme(
version = 5,
bg = "#FFFFFF",
fg = "#333333",
primary = "#2c3e50",
base_font = font_google("Lato"),
heading_font = font_google("Montserrat")
),
...
)
```
**Auto-brand from `_brand.yml`:**
If a `_brand.yml` file exists in your app or project directory, `bs_theme()` automatically discovers and applies it. No code changes needed. Requires the `brand.yml` R package.
```r
bs_theme(brand = FALSE) # Disable auto-discovery
bs_theme(brand = TRUE) # Require _brand.yml (error if not found)
bs_theme(brand = "path/to/brand.yml") # Explicit path
```
## Theming Workflow
1. Start with the `"shiny"` preset (default) or a Bootswatch theme close to your desired look
2. Customize main colors (`bg`, `fg`, `primary`)
3. Adjust fonts with `font_google()` or other font helpers
4. Fine-tune with Bootstrap Sass variables via `...` or `bs_add_variables()`
5. Add custom Sass rules with `bs_add_rules()` if needed
6. Enable `thematic::thematic_shiny()` so plots match the theme
7. Use `bs_themer()` during development for interactive preview
**Example:**
```r
theme <- bs_theme(preset = "minty") |>
bs_theme_update(
primary = "#1a9a7f",
base_font = font_google("Lato")
) |>
bs_add_rules("
.card { box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
")
```
## bs_theme()
Central function for creating Bootstrap themes. Returns a `sass::sass_bundle()` object.
```r
bs_theme(
version = version_default(),
preset = NULL, # "shiny" (default for BS5+), "bootstrap", or Bootswatch name
..., # Bootstrap Sass variable overrides
brand = NULL, # brand.yml: NULL (auto), TRUE (require), FALSE (disable), or path
bg = NULL, fg = NULL,
primary = NULL, secondary = NULL,
success = NULL, info = NULL, warning = NULL, danger = NULL,
base_font = NULL, code_font = NULL, heading_font = NULL,
font_scale = NULL, # Scalar multiplier for base font size (e.g., 1.5 = 150%)
bootswatch = NULL # Alias for preset
)
```
Use `bs_theme_update(theme, ...)` to modify an existing theme. Use `is_bs_theme(x)` to test if an object is a theme.
### Presets and Bootswatch
**The "shiny" preset (recommended):** `bs_theme()` defaults to `preset = "shiny"` for Bootstrap 5+. This is a polished, purpose-built theme designed specifically for Shiny apps — it is **not** plain Bootstrap. It provides professional styling with well-chosen defaults for cards, sidebars, value boxes, and other bslib components. Start here and customize with colors and fonts before reaching for a Bootswatch theme.
**Vanilla Bootstrap:** Use `preset = "bootstrap"` to remove the "shiny" preset and get unmodified Bootstrap 5 styling.
**Built-in presets:** `builtin_themes()` lists bslib's own presets.
**Bootswatch themes:** `bootswatch_themes()` lists all available Bootswatch themes. Choose one that fits the app's purpose and audience — don't apply one by default.
Popular options: `"zephyr"` (light, modern), `"cosmo"` (clean), `"minty"` (fresh green), `"flatly"` (flat design), `"litera"` (crisp), `"darkly"` (dark), `"cyborg"` (dark), `"simplex"` (minimalist), `"sketchy"` (hand-drawn).
### Main Colors
The most influential colors — changing these affects **hundreds** of CSS rules via variable cascading:
| Parameter | Description |
|---|---|
| `bg` | Background color |
| `fg` | Foreground (text) color |
| `primary` | Primary brand color (links, nav active states, input focus) |
| `secondary` | Default for action buttons |
| `success` | Positive/success states (typically green) |
| `info` | Informational content (typically blue-green) |
| `warning` | Warnings (typically yellow) |
| `danger` | Errors/destructive actions (typically red) |
```r
bs_theme(
bg = "#202123", fg = "#B8BCC2",
primary = "#EA80FC", secondary = "#48DAC6"
)
```
**Color tips:**
- `bg`/`fg`: similar hue, large luminance difference (ensure contrast for readability)
- `primary`: contrasts with both `bg` and `fg`; used for hyperlinks, navigation, input focus
- Colors can be any format `htmltools::parseCssColors()` understands
### Typography
Three font arguments: `base_font`, `heading_font`, `code_font`. Use `font_scale` to uniformly scale all font sizes (e.g., `1.5` for 150%).
Each argument accepts a single font, a `font_collection()`, or a character vector of font names.
#### font_google()
Downloads and caches Google Fonts locally (`local = TRUE` by default). Internet needed only on first download.
```r
bs_theme(
base_font = font_google("Roboto"),
heading_font = font_google("Montserrat"),
code_font = font_google("Fira Code")
)
```
With variable weights: `font_google("Crimson Pro", wght = "200..900")`
With specific weights: `font_google("Raleway", wght = c(300, 400, 700))`
**Recommend fallbacks** to avoid Flash of Invisible Text (FOIT) on slow connections:
```r
bs_theme(
base_font = font_collection(
font_google("Lato", local = FALSE),
"Helvetica Neue", "Arial", "sans-serif"
)
)
```
Font pairing resource: fontpair.co
#### font_link()
CSS web font interface for custom font URLs:
```r
font_link("Crimson Pro",
href = "https://fonts.googleapis.com/css2?family=Crimson+Pro:[email protected]")
```
#### font_face()
For locally hosted font files with full `@font-face` control:
```r
font_face(
family = "Crimson Pro",
style = "normal",
weight = "200 900",
src = "url(fonts/crimson-pro.woff2) format('woff2')"
)
```
#### font_collection()
Combine multiple fonts with fallback order:
```r
font_collection(font_google("Lato"), "Helvetica Neue", "Arial", "sans-serif")
```
## Low-Level Theming Functions
For customizations beyond `bs_theme()`'s named parameters. These work directly with Bootstrap's Sass layers.
### bs_add_variables()
Add or override Bootstrap Sass variable defaults:
```r
theme <- bs_add_variables(
bs_theme(preset = "sketchy", primary = "orange"),
"body-bg" = "#EEEEEE",
"font-family-base" = "monospace",
"font-size-base" = "1.4rem",
"btn-padding-y" = ".16rem"
)
```
**The `.where` parameter** controls placement in the Sass compilation order:
| `.where` | When to use |
|---|---|
| `"defaults"` (default) | Set variable defaults with `!default` flag. Placed **before** Bootstrap's own defaults. |
| `"declarations"` | Reference other Bootstrap variables (e.g., `$secondary`). Placed **after** Bootstrap's defaults. |
| `"rules"` | Placed after all rules. Rarely needed. |
**Referencing Bootstrap variables:**
```r
# This fails in bs_theme() because $secondary isn't defined yet:
# bs_theme("progress-bar-bg" = "$secondary")
# Use bs_add_variables with .where = "declarations" instead:
bs_theme() |>
bs_add_variables("progress-bar-bg" = "$secondary", .where = "declarations")
```
### bs_add_rules()
Add custom Sass/CSS rules that can reference Bootstrap variables and mixins:
```r
theme <- bs_theme(primary = "#007bff") |>
bs_add_rules("
.custom-card {
background: mix($bg, $primary, 95%);
border: 1px solid $primary;
padding: $spacer;
@include media-breakpoint-up(md) {
padding: $spacer * 2;
}
}
")
```
From external file: `bs_add_rules(sass::sass_file("www/custom.scss"))`
Available Sass functions: `lighten()`, `darken()`, `mix()`, `rgba()`, `color-contrast()`.
Available Bootstrap mixins: `@include media-breakpoint-up()`, `@include box-shadow()`, `@include border-radius()`.
### bs_add_functions()Related in Ads & Marketing
ads
IncludedMulti-platform paid advertising audit and optimization skill. Analyzes Google, Meta, YouTube, LinkedIn, TikTok, Microsoft, and Apple Ads. 250+ checks with scoring, parallel agents, industry templates, and AI creative generation.
banana
IncludedAI image generation Creative Director powered by Google Gemini Nano Banana models. Use this skill for ANY request involving image creation, editing, visual asset production, or creative direction. Triggers on: generate an image, create a photo, edit this picture, design a logo, make a banner, visual for my anything, and all /banana commands. Handles text-to-image, image editing, multi-turn creative sessions, batch workflows, and brand presets.
rpg-migration-analyzer
IncludedAnalyzes legacy RPG (Report Program Generator) programs from AS/400 and IBM i systems for migration to modern Java applications. Extracts business logic from RPG III/IV/ILE source code, identifies data structures (D-specs), file operations (F-specs), program dependencies (CALLB/CALLP), and converts RPG constructs to Java equivalents. Generates migration reports, complexity estimates, and Java implementation strategies with POJO classes, JPA entities, and service methods. Use when modernizing AS/400 or IBM i legacy systems, analyzing RPG source files (.rpg, .rpgle, .RPGLE), converting RPG to Java, mapping data specifications to Java classes, planning legacy system migration, or when user mentions RPG analysis, Report Program Generator, RPG III/IV/ILE, AS/400 modernization, IBM i migration, packed decimal conversion, or mainframe application rewrite.
brand-library-architect
IncludedBuild a complete brand library for a product — visual asset render pipeline, brand documentation set (BRAND, COPY, MANIFESTO, BIOS, FAQ, GLOSSARY, TONE, PRICING), open-source convention files (README, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT), and a self-contained press kit. This skill should be used when the user asks to "build a brand library / brand kit / press kit / brand assets" for a product, "set up a brand library workflow," "create a positioning manifesto plus visual identity," or any combination of brand documentation + visual asset pipeline. Apply phase-by-phase or run end-to-end. Templates are product-agnostic and use {{TOKEN}} placeholders the skill prompts the user to fill.
writing-tech-post
IncludedAuthors engineering blog posts end-to-end: launch deep-dives, incident postmortems, architecture migrations, performance case studies, tutorials, AI/agent system writeups, security disclosures, and research-to-product translations. Picks the correct archetype, plans the abstraction ladder, enforces an evidence cadence (diagrams, benchmarks, profiles, traces, code, ablations), tunes voice against publisher house styles (Datadog, Vercel, GitHub, AWS, Meta, Cloudflare, Jane Street), and runs a pre-publish gate for narrative momentum and disclosure ethics. Use when drafting a new engineering post, restructuring a draft that feels flat, deciding which evidence form belongs where, validating that depth and product context are balanced, or preparing a postmortem, migration, or performance narrative for external publication. Do not use for API reference documentation, README authoring, marketing copy, release notes, generic SEO content, ghost-written executive thought leadership, or non-engineering long-form essays.
blog-google
IncludedGoogle API integration for blog performance: PageSpeed Insights, CrUX Core Web Vitals with 25-week history, Search Console performance, URL Inspection, Indexing API, GA4 organic traffic, NLP entity analysis for E-E-A-T, YouTube video search for embedding, and Google Ads Keyword Planner. Progressive feature availability based on credential tier (API key, OAuth/service account, GA4, Ads). Shares config with claude-seo at ~/.config/claude-seo/google-api.json. Use when user says "google data", "page speed", "core web vitals", "search console", "indexation", "GA4", "keyword research", "nlp entities", "blog performance", "youtube search", "google api setup".