
Joe Cheng | Styling Shiny apps with Sass and Bootstrap 4 | Posit (2020)
Customizing the style–fonts, colors, margins, spacing–of Shiny apps has always been possible, but never as easy as we’d like it to be. Canned themes like those in the shinythemes package can easily make apps look slightly less generic, but that’s small consolation if your goal is to match the visual style of your university, corporation, or client. In theory, one can “just” use CSS to customize the appearance of your Shiny app, the same as any other web application. But in practice, the use of large CSS frameworks like Bootstrap means significant CSS expertise is required to comprehensively change the look of an app. Relief is on the way. As part of a round of upgrades to Shiny’s UI, we’ve made fundamental changes to the way R users can interact with CSS, using new R packages we’ve created around Sass and Bootstrap 4. In this talk, we’ll show some of the features of these packages and tell you how you can take advantage of them in your apps. Resources: https://speakerdeck.com/jcheng5/styling-shiny
image: thumbnail.jpg
Transcript#
This transcript was generated automatically and may contain errors.
Okay, our next speaker probably doesn't need an introduction, but I was told I'm supposed to introduce people, so I'm going to do it. It's Joe Cheng. He is the CTO of our studio, and he is the original creator of Shiny. Today he's going to be talking about Styling Shiny apps with Sass and Bootstrap 4.
Yeah, today we're going to talk about Styling Shiny apps, and I'm going to start by talking about the current state of the world when it comes to Styling Shiny. So here are just some of the approaches that I personally have seen people using. The first and probably most common approach to Styling Shiny apps is to not do it. Just accept the defaults. And honestly, how many of us are just happy it works at all?
So this is probably the simplest possible example of a Shiny app, but even that, you know, it's something presentable. You can show it to your coworkers. And certainly as of 2012, when Shiny was new, this was pretty exciting and modern-looking. Less so in 2020. And you can take that lack of styling pretty far. I mean, this is an app called Radiant by Vincent Nyes, and it is using only the default style, and he's able to do quite a lot with this app.
Today I'm going to focus on this example app. This is an evolution of an app that I used for a talk last year. I'm not going to get into the functionality, but I've made it look a little bit different than a regular Shiny app or the most common kinds of Shiny app in terms of structure, but the styling is still the same. This is the default style. I haven't changed any of the fonts or colors or anything like that.
So Shiny's default UI is powered by Bootstrap, which is not a piece of software that we created. It was created originally at Twitter and is now independent. And this is one of the most popular pieces of software in the world today. It is the number six most popular GitHub project by stars across all of GitHub. And it was designed to be easily customized, but easily customized by web designers. They weren't thinking of our users, unfortunately.
And this is an example of what Bootstrap looks like. This is an example from Bootstrap's documentation of just a simple page. And there are a lot of things about this page that, I mean, it looks nice, maybe a little bit generic, but this is that same page with the one change of Bootstrap being removed. It looks like this. So you can see that the amount of work that Bootstrap is doing for us is substantial. It's taking care of typography. It's doing layout. There are buttons and nav bars and search. All of that are components that come with Bootstrap, along with these different colors.
The problem with custom CSS
So that's that first approach is just accepting the defaults. And I'm sure many people here how many people here are developing Shiny apps or have developed a Shiny app? And how many of you have just accepted the defaults and shipped apps?
So the second approach is maybe you're not happy with the complete default look. You just want to punch it up a little bit, you know? And one way to do that is to use a prefabricated theme, such as the ones that come with shinythemes, the shinythemes package. So this, again, is that generic example with Cranwells. And with a single argument to FluidPage, you can apply a different theme. And you can see the colors change, the fonts changed, the spacing has changed very slightly, theme capitalization. And all of that is accomplished by just that one argument.
And there are a few, maybe one or two dozen different themes that are available. And there's some variety there. But you're making sort of an all or nothing choice. So if you like one of these, great. But you're going to take the entire style that that offers you. And there's not really a lot of handholding it does to customize it on top of that.
You might also want it to look a lot different. And a lot of people, I think, are doing that these days. How many people here are using something like Shiny Dashboard, Shiny Material, a different Oh, my gosh. So quite a lot of you. And I think this took us a little bit by surprise, because we had created Shiny Dashboard at our studio in order to actually create dashboards. And then to see what people are using it for, which is almost everything that people build with Shiny apps, has been surprising. But I think it was because people just want a different look than the default.
So this is Shiny Dashboard. This is Shiny Material by Eric Ray Anderson that makes things look more googly, I guess. And then this is from the R Interface project, an Argon-based UI toolkit. And Shiny was always designed to do this. It was always designed to let people use things besides our defaults.
But what it doesn't do, necessarily, is provide this capability, which is you want specific fonts, colors, and styles. But I think we all who have tried to do this can agree, shouldn't this be easier than it is today with Shiny? Definitely requires a lot of custom CSS.
And before I start talking about that, I just want to give a nod to this last approach, which is native HTML. I'm not going to talk much about this today. But you do always have the option to forego most of Shiny's UI layout CSS style and do your own thing or work with a web designer who can do this for you. Let R do what R is good at and let HTML and CSS designers do what they're good at.
The thing I want to focus on today is this, right? How can I get my Shiny app to match the official fonts and colors of my company? I get this question a lot. I get it from our customer success people. I get it from people at conferences. And maybe for you, it's not your company. Maybe it's your client. Maybe it's your university. Or whatever it is. But have some reason to prefer a specific set of fonts and colors. And this seems like it's something that should be pretty easy. And it turns out that it's not.
And this, I hope, will be the most painful demo that you'll see here at RStudioConf. Here again is my app. And our designer, Greg, sent me this color and font branding guide for RStudio. And I want to take this Cranwheels app and make it look less generic and more RStudioish. So the first thing I want to do is I want to take this bright blue and change this to this accent orange, which has a hex color of whatever it is. So I'm going to do this the way you would do this today, by writing more CSS.
I've already created the CSS file. And if you have some experience with web stuff, you've done this before, you use the inspector to figure out what kind of a selector you might want to write. And because I did this for many years, I can tell you that that is the right incantation for this. So that color changed. It took me a lot longer than that to do it the first time. But sure enough, that works.
But these links, let's update those links as well, right? So I'll say those are navs that are not active. And instead of changing the foreground color, I mean, the background color, I'm now going to change the foreground color. Now, you might think you're done. But actually, there are a few things missing. If I scroll down, there's a link here at the bottom that's still blue. If I hover over these... when I hover over these links, they're supposed to turn a slightly darker shade, and it doesn't do that. And if I click on... I don't know if you can see it with the projector, but when I click on a control, a focus ring is drawn, and that also is this blue color. And I want to replace all of those with orange.
And the point is, I'm just trying to replace one color here. And I'm already falling into this rabbit hole of having to hunt down one little edge case after another and write CSS to overwrite it all. So this is not a great status quo that we're living in today.
And the point is, I'm just trying to replace one color here. And I'm already falling into this rabbit hole of having to hunt down one little edge case after another and write CSS to overwrite it all.
Why CSS is hard: enter Sass
So why is this so difficult? Why does it take so much effort to just override one color? This is bootstrap CSS. There's a color that's being highlighted. There's a hex code. And this is the source code for bootstrap CSS. So this is saying that this link should be blue. That very specific shade of blue. This color appears all throughout the CSS file 25 different times. And some of them are pretty complicated. So if you as a Shiny app author want to change all of those instances of blue to that specific shade of orange, you would actually have to overwrite each one of those rules with custom CSS. And those are pretty complicated selectors that we're looking at here. So clearly this is not really a tenable strategy for changing colors.
And in fact, imagine if you're not a Shiny app author, but instead you are the people maintaining bootstrap. If you are the developer of bootstrap, I mean, how much more painful is it for you as you're working on this framework? And it turns out that that is not what people do. People do not do search and replace 25 times to change colors. Instead, professional web developers use a language called Sass instead of CSS. Sass is CSS plus superpowers.
Here you can see on the left, we've got Sass, which is CSS plus additional features. And the feature that we care about for today's purposes is Sass has the ability for you to declare and use variables. So instead of hard coding this color throughout the CSS, you write Sass code that declares a variable called primary, assigns it that color blue, and then everywhere in your Sass code, you use that variable instead of the hard-coded color. Now browsers don't understand Sass. So you have to run that Sass source through a Sass compiler, and you result in this style.css that replaces the variable with its value. You get the picture.
And really, this Sass compiler is where we lose our users most of the time. Because we are now talking about a tool chain that they are not familiar with and not really interested in using. But the point I want to get across is that Bootstrap was designed from the beginning for you to replace this color. They centralized in their Sass code that blue color into one variable. And then that variable is used throughout their Sass source. The upshot of this is that as our users, if we could set those Bootstrap variables, if we could override the values of those variables from R, then we could bend Bootstrap to our will. I want primary to be orange, and we would be done.
Introducing bslib
So to that end, we've developed two new R packages that I'm talking about today. Well, I'm not going to talk about Sass, but Sass is the foundation that Bootstrap lib is built on. Sass is a way to compile Sass to CSS directly from R, but we've built these higher-level tools specifically for Bootstrap in Bootstrap lib. So you're not going to need to interact with Sass in order to use Bootstrap lib. In the rest of my talk, I'm mostly going to be focusing on Bootstrap lib.
Bootstrap lib, you can think of it as R bindings for Bootstrap. It's designed to be used not only for Shiny, even though we're just going to be talking about Shiny today, but you can also use it with R Markdown, with HTML widgets, anywhere else in R where you might want to use Bootstrap. You can customize and re-customize Bootstrap straight from within R, and it also basically bundles in what used to be the separate package of shinythemes. This is functionality that's included in Bootstrap lib, and I'm very excited to show you this.
So this is how you use it. This is a typical Shiny app. First, you load the library. Second, you call bstheme new, and this declares that you are starting a new theme. It's kind of equivalent to calling plot.new. Step three, you set whatever customizations you'd like. In this case, I'm setting the body background color to pink and that primary link color to maroon. And then finally, you call the function called Bootstrap to build a custom build of Bootstrap that obeys those variables and inserts it into whatever UI you're calling it from.
So let's take a quick look at that. So I'm going to take this Cranwhales app again, and I've already done three of those steps. So library Bootstrap lib is here. I'm calling bstheme new, and I'm calling Bootstrap Bootstrap. So now all I need to do is add a variable. So the first thing we're going to do is we're going to change that primary color to that orange, and let's run the app. And sure enough, with that one change, the links are orange. This is orange. The link at the bottom is orange. If I click on this selector or this text input, the ring around it is orange. So everywhere in those 25 instances of blue, those are now orange.
But let's go a step further, and let's take this gray background and change that to our studio dark blue. So bs, theme, add variables, primary, and secondary. And that's all we need to do. Which, let's be honest, isn't that kind of what you expect from Shiny in the first place? Shouldn't it always have been just, like, two variables?
Which, let's be honest, isn't that kind of what you expect from Shiny in the first place? Shouldn't it always have been just, like, two variables?
Which, if you're a skeptic, you might have been rolling your eyes a little bit that I just knew that primary and secondary were the names of those variables. So how do we know what variables to type in? So there are links here to the variable list. I'll have a link to these again at the end. So there are hundreds of variables, but they're very sensibly named and grouped in the bootstrap source code. In addition, we've written our own custom documentation for you fine folks in the Shiny community that are more along the lines of things that you might need to do. And these docs will be evolving over time as well.
Real-time theme previewing
One other thing that we have done to try to make this easier for you is we've built a real-time previewing tool. So instead of calling run app from Shiny, we call run with themer. And this launches that exact same app, but with this additional UI on the right. So in this case, like, let's change the background color to something ridiculous. And within, you know, a second or so, it updates. So let's change this to something reasonable, like RStudio official light gray. And then why don't we go all the way and change the font to the RStudio font.
So that's looking pretty good now. And what this tool does is it prints out the source code that you need to make this permanent to the console. So I'll just copy this line. Add it here. And the next time I run this app without run with themer, you can see that it is updated.
Plot auto colors
Now, one thing that's always bothered me personally about any time I try to punch up my Shiny apps, everybody likes dark mode, right? That's cool. But look at the background of this ggplot. It looks ridiculous. Right? The white around it just kind of throws off the whole effect.
So what we've done with the next version of Shiny, hopefully, assuming Winston approves it, is a feature that you need to opt into called plot auto colors. So with that one line of code, everywhere in your application you have a ggplot or a base plot or a lattice, we will try to apply some automatic defaults based on the colors in your DOM. And this is actually pretty sophisticated, and it has nothing to do with bootstrap. This is built into Shiny, not bootstrap lib. So you can take advantage of this feature when Shiny ships it with any kind of UI framework, including Shiny dashboard, flex dashboard, any of those others.
So those are the instructions. Either you can set it application-wide with that Shiny option, or you can pass individual auto colors equals true to render plot if you want to opt in or out on a plot-by-plot basis.
Third-party packages and Bootstrap 4
Before we get to questions, I just want to point out that third-party packages that want to take advantage of bootstrap and bootstrap lib coloring, they will have to be updated. So HTML widgets will need to be updated if they want to take advantage of this functionality. Our markdown document formats and Shiny extensions, like ShinyBS and Shiny widgets. The ones of these that RStudio maintains, we will take care of updating. And for the ones that are maintained by third-party package maintainers, if you're in this room, we would love to work with you, we would love to support you as we try to get people onto this infrastructure.
I just want to acknowledge that bootstrap lib is built using both bootstrap 3 or bootstrap 4, which is the first time that RStudio has taken the plunge into bootstrap 4. I'm not going to talk much more about this today unless people have specific questions, but I just want to point out that this is laying the groundwork towards moving everything to bootstrap 4, which is something that is going to happen over the course of the next year or so.
Finally, I want to acknowledge, number one, some prior art. We didn't know about this package until we were pretty far along in the development, but there's a package from the DreamRs organization called Fresh that accomplishes many of the same goals, but without the sort of benefit of being the maintainers of Shiny. So I think they've done some really clever work that's very cool, and it works with a lot of packages already today. And I think because we were able to kind of change things from the inside, we were able to achieve a slightly different workflow that hopefully will build a better foundation for the future. But I just want to acknowledge that they've done some excellent work there.
And then Sass was primarily implemented by Tim Masny, who was a summer intern, and bootstrap lib was mostly written by Carson Seabrook, who will be speaking next on Shinymeta. Thank you.
Q&A
Thanks, Joe. That's great. Thanks for applying public pressure on my ability to make a choice about this. All right. So let's get the questions here. Why is bootstrap lib all written in Snakecase? All written in Snakecase? You know why.
So why is it written in Snakecase? Shiny was written in CamelCase because in 2012, I think, I didn't have strong opinions and I was just kind of going by what I thought most people wanted. And I get that question a lot. Like, why aren't we moving everything to Snakecase? So it's very hard to move Shiny itself to Snakecase because of backwards compatibility. But where we have the opportunity to create brand new packages, you know, why not go with at least what it seems to me like the Shiny community is asking for, which is more not only Snakecase, but moving to sort of like input underscore select instead of select input.
Do Plot Autocallers propagate to Plotly plots? No. There's no reason why they couldn't. But currently, we've been focused on the static side of things.
When will Shiny move to Bootstrap 4? Yeah, that was going to be my entire talk today. And then it turned out that the theming stuff took all 20 minutes. It's going to be a process over this year, but we are intent on moving in a way that does not break backwards compatibility. So I think we've landed on a strategy that is going to let us very organically allow people to move to Bootstrap 4 without ever breaking the promises that we've made. So I think it's something that you should expect to see evolve over the next 12 to 24 months.

