This article is for Shiny and R Markdown developers who wish to write custom HTML components that “just work” with . Readers should already have some basic understanding of the Sass language as well as the sass package.
A basic themeable component
Before going through a full-blown dynamically themeable custom
component, let’s start from a relatively straight-forward example of
implementing a custom person()
component. Say we have the
following R function to generate some HTML with classes that we’ll write
custom Sass/CSS styles for:
person <- function(name, title, company) {
div(
class = "person",
h3(class = "name", name),
div(class = "title", title),
div(class = "company", company)
)
}
And here’s some custom Sass to style those classes. Since these Sass
rules listen to Bootstrap Sass variables like $gray-600
,
person()
styles works great with different
bs_theme()
input:
.person {
display: inline-block;
padding: $spacer;
border: $border-width solid $border-color;
@include border-radius;
@include box-shadow;
outline: 0;
width: 300px;
.title {
font-weight: bold;
}
.title, .company {
color: $gray-600;
}
margin: $grid-gutter-width;
margin-right: 0;
// On mobile, span entire width
@include media-breakpoint-down(sm) {
display: block;
width: auto;
margin-right: $grid-gutter-width;
}
}
.person:last-of-type {
margin-right: $grid-gutter-width;
}
If we were to save these Sass rules to a file named
person.scss
, then we can then bs_add_rules()
to the bs_theme()
and use our themeable
person()
component like so:
Dynamically themeable component
To make the custom person()
component
dynamically themeable (i.e., make it work with
session$setCurrentTheme()
), we need an R function that
generates an htmltools::htmlDependency()
from a given
theme
. While not required, suppose this function,
person_dependency
, lives in an R package called
{mypkg}
which includes the person.scss
(and
pre-compiled person.css
) file under the inst/
directory. Then we could do the following:
name <- "person"
version <- "1.0.0"
person_dependency <- function(theme) {
if (is_bs_theme(theme)) {
scss <- system.file(package = "mypkg", "person.scss")
bs_dependency(
input = sass::sass_file(scss),
theme = theme,
name = name,
version = version
)
} else {
htmlDependency(
name = name,
version = version,
stylesheet = "person.css",
package = "mypkg",
all_files = FALSE
)
}
}
#' @export
person <- function(name, title, company) {
div(
class = "person",
h3(class = "name", name),
div(class = "title", title),
div(class = "company", company),
bs_dependency_defer(person_dependency)
)
}
Note that when theme
is a bs_theme()
object, then person.scss
is compiled with Bootstrap Sass
variables and mixins included via bs_dependency()
(which
returns the compiled CSS as an htmlDependency()
).
Otherwise, if theme
is not a
bs_theme()
object, then person()
is being used
in a context where bslib is not relevant, so a
pre-compiled CSS file is returned instead. Pre-complied CSS isn’t
necessarily a requirement, but it’s a good idea for increasing
performance and reducing software dependencies for end users.
HTML widgets
For htmlwidgets that can be themed via CSS, we
recommend supplying a bs_dependency_defer()
to the
dependencies
argument of createWidget()
(similar to the person()
component from the last section),
which will make the widget dynamically themeable. For widgets that can
not be themed via CSS, the best option may be to query the
active theme inside a preRenderHook()
via
bs_current_theme()
, and then translate any relevant
information to the widget’s instance data, for example:
my_widget <- function(...) {
createWidget(
name = "mywidget", ...,
preRenderHook = my_widget_hook
)
}
my_widget_hook <- function(instance) {
theme <- bslib::bs_current_theme()
if (!bslib::is_bs_theme(theme)) {
return(instance)
}
instance$x$theme <- modifyList(
instance$x$theme, as.list(
bslib::bs_get_variables(theme, c("bg", "fg"))
)
)
instance
}