Resources

How to bring modern UI to your Shiny apps

Looking for ways to make your Shiny apps a little…shinier? Join Garrett Grolemund at Posit on Wednesday, January 31st at 11 am ET to learn how to theme and brand your own apps. The session will highlight how to: Layout an app with the bslib package (modern UI toolkit with no knowledge of CSS required) Add cards, value boxes, and logos Customize the theme of the app Tweak the theme by swapping out primary colors, secondary colors, and more. Quickly apply the theme to every plot in the app Work with bootstrap classes Helpful Resources: ️ Code at : https://github.com/garrettgman/shiny-styling-demo bslib package: https://rstudio.github.io/bslib/ bsicons package: https://github.com/rstudio/bsicons ️ Bootstrap icons: https://icons.getbootstrap.com/ ️ Bootstrap CSS classes: https://bootstrapshuffle.com/classes thematic package: https://rstudio.github.io/thematic/ gitlink package: https://github.com/colearendt/gitlink ️ Follow-up links: Posit Team: https://posit.co/products/enterprise/team/ Request evaluation: pos.it/chat-with-us Posit Team demo resources: pos.it/demo-resources LIVE Q&A ROOM for ~11:45 am on January 31st: https://youtube.com/live/1G8ZM6kbt8c?feature=share There is no need to register; join us here on YouTube at the time above or you can add to your calendar using the link below: pos.it/team-demo We host these Workflow Demos on the last Wednesday of every month, so you can use the link above to add the recurring event as well

Jan 31, 2024
49 min

image: thumbnail.jpg

Transcript#

This transcript was generated automatically and may contain errors.

Hi everybody, happy Wednesday and thank you for joining us for our monthly Workflows with Posit Teams session. By popular demand, today we're going to talk about using your company brand in a Shiny app. While this example highlights Shiny, you can also use all of these techniques with Quarto and R Markdown documents. I've found that incorporating your company brand can make an impact on your data storytelling, but making a great first impression. You can elevate the initial perceived quality of your content and encourage greater adoption.

So I'm really excited for us to go through this example today. And as a reminder, we host these sessions on the last Wednesday of every month at the same time and same place here on YouTube, where you can find the recordings of the nine previous sessions as well. If you have suggestions or a workflow that would help make life a bit easier for you, please let us know in the YouTube chat here or over on Slido if you prefer to be anonymous. You can use YouTube and Slido to ask questions during the demo today as well. At the end of the demo, you'll be automatically directed over into another YouTube live room for our Q&A. With that, I would love to introduce Garrett, Director of Developer Relations at Posit and one of the earliest employees at RStudio. Among many other things, Garrett is the co-author of R for Data Science and an author of the Lubridate package. Thank you so much, Garrett. I'll turn it over to you.

Overview of the demo

We're going to start with a basic app that looks like this and slowly turn it into an app that looks like this. Along the way, we'll lay out the components of the app with the bslib library. We'll add cards and value boxes with icons like you see here. We'll add a logo. We'll change the color theme to use brand colors. We'll apply the theme to our plots and we'll change the fonts that appear throughout the app. I'll also link to GitHub in my app and publish the app to Posit Connect. Now this app is written in R and that will be our focus today. However, everything that we can do, you can also do with the Shiny app written in Python, but of course the code that you would use would be different.

So let's get started. And that means logging in to an IDE where we could develop our code. For me, that means Posit Workbench. And inside of Posit Workbench, I prefer to work in the RStudio Pro IDE. So I'll make that selection and start a new section for us to work in. So Workbench launches and here's the RStudio IDE. The next thing I need to do is get the code that contains my app. So I've already been working on this app, not necessarily collaboratively, but I have prior art that I want to start with. And that code lives on GitHub in this public repo. It's garrettgman at shiny-styling-demo. So if you wanted, you could download this code for yourself, clone the Git repository. And that's what I'm going to do. I'm going to clone it into my Workbench session. To do that, I'll copy the URL. I'll go back to RStudio and I'll make a new project. When I make that project, I can choose to make it from a version control repo. In this case, it's a Git repo. And I paste in the GitHub repo. I also need to choose a directory name to clone that repo into in my local file structure. I'm going to shiny-styling-demo. So I create that project. RStudio clones the repo, imports the code, refreshes the R session. And now I have all the contents of that GitHub repo. These are local copies that I could work with. And then if I wanted to, I could push them back to GitHub, you know, with typical Git development practices. RStudio adds a Git pane here, which is a GUI pane that I could use to do the typical Git work, like commit, push, pull, create branches, revert, and so on. But today, let's just focus on working with the apps.

Within this repository, you'll find a folder called finished app. This contains the code that creates the finished app we looked at. But we're going to go look at this today because we're going to make this. And we're going to make it from the directory called bare app. That contains the first app we saw. If I run the app, we could take a look at what it does.

So, this app contains data for a fictitious company called DemoCo, or DemoCo. They sell — imagine them selling a freemium software product. Users can use the product for free, but they can also upgrade to a paid professional level service with more features. DemoCo is interested in what they could do to convince their users to upgrade to the paid professional version. So, they've tried out two types of free trials that last a different amount of time. One lasts 30 days, that's trial A. One lasts 100 days. And the app shows data that they've collected over the past year at the success of the free trials at converting users into customers, versus the success of not offering a free trial. It's designed to be a portal that we could give executives so they could query the data themselves. For example, they could filter down to specific customer segments within the data and the app will respond to their selections.

So, that's what the app does, but I don't think this app looks very good. I like the sidebar layout that puts all of the inputs right where you expect to find them, but this endless scroll is not very user friendly. Also, this looks like a basic shiny app out of the box. It doesn't really look like a product that DemoCo might use internally.

So, let's see what we can do to change that.

Setting up the layout with bslib

Now, we're going to start working with this code and to make it easier for us to follow along, I'm going to add an extra column to my IDE. I can do that by going to this panel icon and selecting pane layout, and I'll just add a column. I'll apply that, and then I can put some code side by side, and that's what I'll do. First, I'll take the base code, we'll put it over here. This is our app, and in this untitled document, I'll just copy all this code into it. So, no matter how far afield we go, we at least have some safe ground to return to. We could close the finished app, and we're not going to use these panes too often, so I'll not do that.

I'm using a large font size, so not as much fits in here, but I'm hoping you'll be able to see the typing a little better. So, we want to create a new UI. So, out this goes, and let's just pause for a minute to reflect on what our strategy is going to be. First, we're going to give the app a new layout. Then, we will add a view. Then, we will add a title. Then, we will add cards and value boxes. We'll see what those are as we add them. Next, we'll add a logo. Then, we'll change the theme of the app, and we'll work with the colors in the app. Then, finally, we will work with the fonts in the app, and then super finally, we'll publish the app.

All right. So, how do we do this? How do we change these components of a Shiny app? Well, there's one package in particular that we're going to use to do most of these tasks, and that package is called the bslib package. So, bslib stands for Bootstrap Library, and Bootstrap is a very popular framework for CSS and HTML development, and every Shiny app is built on top of the Bootstrap framework. What bslib does is it provides the most new and up-to-date Bootstrap features in our package so our users can take advantage of those features. It works seamlessly with Shiny apps because it's developed by the Shiny team. It also works with R Markdown documents and even Quarto documents because those documents are also built upon Bootstrap.

In fact, bslib is so integrated in the Shiny that it is imported by the Shiny package. So, if you've installed the Shiny package, you already have it and you can start to use it in your app, and that's what I'll do here. I will tell the app that we're going to start using some functions that come from the bslib package. We'll do that with library bslib, and then the first functions we'll use will recreate the app page for us.

Now, I said that I really like the sidebar layout, and I meant it, and that's what I want to create, a page with a sidebar. Typically, in Shiny, you might do that by calling fluid page, giving it a title panel, and you'll call sidebar layout and give that a sidebar panel and also a main panel, and you'll put your contents inside of there. bslib provides a slightly more efficient way to create a sidebar app. So, let's take a look.

We'll use the function page sidebar, which comes in bslib. It takes a title argument. We don't need to use the title panel helper. We could just pass the title straight to that argument. So, I'll copy and paste that from over here. And next, it takes a sidebar argument, and we use a helper function called sidebar with that argument. Whatever we put inside sidebar will appear in the sidebar, and I have this object called sidebar content. We'll look at it in a second, but it contains everything I want in the sidebar. And then finally, to use page sidebar, anything you want in the main panel, you just list as additional arguments outside of sidebar. Now, if we run this app, we'll see what we get. So, this is our sidebar app, and one thing I want you to notice right off the bat is the styling for this sidebar app is a little more modern, a little more up-to-date versus a typical shiny sidebar app. For example, we have a built-in widget to expand and close the sidebar, and you can see other styling differences there.

So, where did these things come from? If anyone's curious, these are just three widgets and some text, and I saved them as a list in a helper file called setup.r that my app is sourcing. So, down here, you see an object called sidebar content, which is a list. This idea of grouping widgets together into a list is a really helpful pattern if you plan to use the same sidebar content for a lot of your app. So, I'll show you how to do that in a second.

So, if I have a set of widgets in multiple places throughout a shiny app or across shiny apps, I'm just using it today to keep screen real estate focused on the things we're actually going to be touching. So, that's where sidebar content comes from. Now, everything else I just typed, but that's not really what we want in our app. We want these things from our old app over here. Let's pull just a couple of them into our app so we could start working with laying them out before the screen gets too crowded. So, if I put things into my page sidebar, bslib or shiny will add them one beneath the other as if each new item was placed in a new row. And if I continue to do that with all the items I have, I'll be back at that endless scroll, which is what I'm trying to avoid. Really, what I want to do is place the items beside each other as if each new item was a new column. And there's a function in bslib that allows me to do that. It's called layout columns. Let's add that here and see what happens.

Call layout columns and you give it as much content as you want. Close the parentheses. Now, if I run the app, those items are organized into columns.

Adding cards and value boxes

So, this is good. This is a start, but we can see a couple things here. The first thing I see is these are four different items, but the character strings are really just titles for the plots, and they should probably travel together. I don't want them in separate columns. I want each title and plot in a single column and then the next title and plot and so on. In a shiny app, you might collect these things together with something like a well panel, but bslib introduces a new concept called a card that I'm going to use here. If I want to collect two things together in a card, I surround them in the function card. And then if I want the card to have a title or a header, I would wrap that in another function called card header. The reason for that is it'll make it easier for us to style that header a little later on.

All right. So, now our apps and our titles are bound together visually in a way that I think looks really attractive. There's even a drop shadow in there. And we have our app starting to look like where we're headed to. The next thing I think I want to do is I want to change the width of these two plots. I think the line chart deserves more width than the bar chart. It'll just make it easier to see what's going on there. Maybe those dates won't be scrunched together. And so, I'm going to try to make the line chart about twice as wide as the bar chart.

The layout columns function makes it really easy for us to do that. We can do that by adding a new argument called call widths and underscore in there. And we set it to a vector of numbers, one number per column to set the width for. Right now, we have two cards. That means two columns. And I want to make the first card 8 units wide and the next card 4 units wide. Now, why am I choosing those numbers? Layout columns is going to take whatever horizontal space is available to it and divide it into 12 relative units. They're relative because you don't know how wide the user's window is going to be. And the user might actually change the width of the window while they're looking at it. But layout columns will make sure it's always dividing the available space into 12. And now, I'm just apportioning that 12 units between these two cards. And here we go. I've created that width that I like.

But there's more cards to add to this app. And I want the extra cards to be on a new row below these cards. I could do that with the same layout columns function. Here's how it works. If I want to pull over the remaining elements, just start with one element, I'd add it here. I'm going to, as before, wrap these things into a card. And then, as before, I'm going to give this new card a width. Let's say we want this to be four units wide as well. Layout columns divides everything into 12 units. So if you ask for a width that exceeds the 12 units, layout columns will simply put it on a new row below the first. So this four unit wide card will spill over onto a second row. And this is how I create a grid of columns and rows. I just plan out the units and put things where I want them. I could even offset things in here. If you want negative space somewhere in your column or your row, just say how wide you want that space to be and then put a negative number in front of it. So I could insert three units of negative space here. I have four numbers now, only three cards. That's because one of the numbers refers to empty space. And now when I make this app, provide it's wide enough, I see I've moved that recommended trial over. That gives me a lot of freedom. I don't really want to take advantage of that freedom today, but I wanted to show that to you.

What I want to do now is pull in everything and make the final layout. Now, as before, I want to be careful to collect these items together where they belong together. Bear with me while I do that. Make sure I close every new function.

All right, so I have some new cards, so I should apply some new widths here. Let's make every card except the last one, four units wide. That will take us another 12 units. That'll be a second row. And then the last card, I'll have span an entire row by itself. I'll make it 12 units wide. Oh, it looks like I forgot a comma in this case. These are all arguments of layout's columns function, so we got to have commas between each new one.

And here we go. We have our app. Now, I have all the widths the way I like them, but I don't have the heights the way I like them. I prefer to give a little more height to the charts. There's more visual information there. In this table, you know, we could scroll within the card, so I don't mind compressing that a little bit. The way I affect the row heights is by adding a row heights argument to layout columns. So I have three rows. I want them to have relative units of 4, 1.5, and 3. Now, no matter what numbers I put in here, layout columns is going to use the entire vertical space, but by choosing 4, 1.5, and 3, I'm determining how wide those rows are relative to each other. And there we go. Now, this is all laid out, but there is something I see here. I see a lost opportunity. Each of the cards in the center row only shows off one value. bslib provides a new object called a value box that can really emphasize single values, and I'm not using it, but I would sure like to. So let's go back to our code.

The way to use value box is to call the function value box from the bslib library, and value box doesn't require a value header or anything like that. We just give it a title, and then we give it the content we want to appear in that value box. Let's make all three value boxes at the same time, and rerun our app.

And here we go. So now these are value boxes. One of the big advantages of using a value box is you can add an icon to that value box to explain something or draw your user's attention or just make your app look a certain way. So I'm going to add icons to these value boxes.

The value box function takes an argument called showcase, and showcase should be set to some sort of icon. Now, how do you write an icon in R? Well, there's a couple packages that can help you do that, like the font awesome package for font awesome icons, but I'm going to use the BS icons package, which contains bootstrap icons. There's about 5,000 icons that come with bootstrap. As I mentioned, we're using bootstrap all over, so I like the idea of just sticking with those icons. But if there's 5,000 icons, how do I know which ones I want to use? Well, for that, you might turn to Google or the web. I think this is the best website for searching through bootstrap icons. They're all here. You can see there's quite a few, and there's a search box at the top. So if I want an icon related to money, I might do this and see some things. This coin one catches my eye, and so on. I'll try to make sure this link ends up next to the video that you're watching.

All right, so let's go back to our app. Let's make some icons. So I've loaded the package. Now, to pass an icon to showcase, I use this helper function called BS underscore icon, and I give it the name of my icon. So I looked through the icons before we met today, and I decided I'd like to use an icon called stars for this value box, which presents a recommendation. And I would like to use an icon called people-fill for this number of users. And finally, for this average spending, I want to use that coin icon we just looked at. Now I rerun the app, and I give enough space.

We have the icons. Not only that, they introduce some color into our app. This isn't the color I want to use at the end of the day, but anywhere you see color in your app, you can change the color later on to make it fit your theme. So this gives us something to work with.

Now, this is a nice looking app. There's one last thing I want to add to it before I start tweaking the details, and that is my company's logo. We'll put it right here in the sidebar. And to do that, I'm really just going to rely on the fact that this app is an HTML web page. I use HTML to drop an image right in there. And then the user's browser could place that image into the app the way they place an image into any web page that the user looks at.

To make that work takes a two-step process in Shiny. The first step is I have to get the image to the user's web browser. And in Shiny, you do that by supplying a www folder. This is a special folder in Shiny. Everything in the app directory is going to be passed to the server that serves your Shiny app. So the server, for example, will have access to all the helper functions, the data set that it uses to build the plots and whatnot. But it's not going to be passed to your user's web browser. There's no reason to give the user that data. It causes security problems, maybe. It definitely makes the download time increase. It just doesn't make sense, so it doesn't happen. But sometimes you do want to give something to the user's web browser, like a CSS file or an image in our case. And you could do that by putting it in a folder named www. I've put our logo here in this folder along with another file I'll talk about later. Here's the logo. It's just a PNG image. Anything in this www folder will be given to the user's web browser where it's available at the top level of their web page.

All right, so that's step one. The user's go get this image. But where should the browser put it? I have to tell the browser that. And like I said, I'm just going to use basic HTML. Skills are a little undeveloped or rusty, but I think I remember an image tag. Let's use single quotes. Something like image, and then I have to set the source. The source is going to be this file, which I said is available at the top level. I'll set the width. I'm going to make it 100% of the sidebar. And then for the height, I don't really know what it would be, but just, you know, whatever works out when the width's 100%. So I'll use auto. And there I've written the HTML tag. And you can look this up on Google if you need to see how to put an image tag in there. But it's just a character string. If I leave it like this, Shiny's going to think it's a character string that I want Shiny to display in the sidebar. Instead, I want Shiny to treat it as HTML. So I'm going to surround it with this HTML helper function from the Shiny package. Now when I run the app, the app, the browser puts an image right there. It's a little hard to see, but there's some white text in this image. It's the complete logo. But obviously when I came up with this logo, I was envisioning an app with a dark background.

Theming with bslib

So let's shift gears and start tweaking the app, changing the colors, changing the fonts, and getting it ready to publish. How do we change the colors in the app? Well, once again, we're going to use the bslib package. And this time we're going to use an argument called theme that comes in the page function from bslib. And we're going to set it equal to BS theme, also from the bslib package. Within BS theme, we could choose any boot swatch theme, like the Minty theme, for example, and tell bslib to use that theme for our app. A theme controls colors, fonts, all the styling in your app.

Let's suppose we don't know any themes for us. Boot Swatch comes with another terrific function called bsthemer, and you can put it inside your server function. Then when you run your app, your app will have a widget up here in the corner that allows you to try out different themes to see how it looks in your app. So for example, we could try out the superhero theme. Kind of nice, darker. We could try out any of these. These are all Boot Swatch themes that come with our app. This one might be good if we're wireframing something to have discussions about it. I like the Darkly theme, and that's where I'm going to take this out. I'm going to start here. But I don't like it out of the box. I do want to change things in the theme, and I could do some of that right here with bsthemer.

For example, I could change the foreground color to a color of my choosing. So let's keep this so I can paste it back in here in a second. But I know that this light blue from DemoCo is a color that I believe is... Here, let me look it up in my notes so I don't get there. Yes, it's 867ED. So I could take the Darkly theme and change the foreground color to be this color. I like how these letters are this color. It really pulls the branding through, but I really hate how these fields in the sidebar are that color. I really expect those to stay white. So maybe I'm not going to do this. Instead, I'll return this to what it used to be. But I have other ways to put the color in here. There's several accent colors used by every app, and I could change those. Although the way I've written this app, it's not really taking advantage of those accent colors, but I could change them. I could change the fonts of the app as we go. That's not something I'll do here, but it's an option. I could change other options. For example, I could turn box shadows and maybe mess with the spacing.

And let's change one accent color. Wonder what happens if I change that to G. Well, I crashed the app. But every time I made a change in the app, in the console back here, we weren't looking at it, but the themer was printing the code we would need to apply that custom scheme we came up with to our app. We could take these arguments that happen after theme, and we could put them right into VS theme up here. And then we'd have the theme that we were looking at. Now, I didn't really like anything I was doing there, but I did like that there's a darkly theme, and my app looked good with it. So I'll start there. And instead of using the themer to change the components of my app, I think I'll manually change the components.

Customizing colors with CSS classes and variables

Here's my app with the darkly theme. Here's what I want to do to it. I want to make all this text blue while keeping this white. I want to make some of these backgrounds a different color. I want to change the fonts to reflect the fonts in the logo. Essentially, I want to touch everything in this app to make it a little different. And by doing that, we'll be able to survey together all our options for changing styling piece by piece inside our shiny app. Sounds like a lot, but it's not. Let's get started.

So the first thing we can do, the easiest thing might be to change the background of the value boxes. Each value box takes a theme argument, and I could set that to a hard-coded color, or even one of those colors that we were looking at before, like primary, secondary, success. These are all colors defined with each theme. And if I reload this app, I'll see that this value box has the success color. So now it's showing up in our app. The other value boxes, I'm going to make the secondary color. It's a shade of gray. It doesn't look very different from before, but to my eyes, it looks a little more inviting, a little warmer.

There we go. We have the secondary color there. Now, I don't like this green color. It has nothing to do with my brand, but it does give me a method for changing this color here. I could change the color value of the secondary color, the primary color, any of the colors in the theme in the BS theme function. So if I want the success color to be my 86c7ed color, I could set it here. Now, everywhere that success color appears in the app, it'll be that blue, which means I've now made that value box my company color, which is great.

Next, I'll use that success color to make the other parts of this app the company color as well. So going back to here, we have the card headers. That's where our text appears. The method I'm going to use to change the color of the card headers will be to assign them a CSS class. A CSS class is just a set of styling rules like color, font, family, size, whatever, that gets applied to the objects that share that class. The bootstrap framework is just a collection of many, many CSS classes that all do different things, but all look good together. If I can find a bootstrap class that changes the text to the success color, then I could use that class here because card header has a class argument.

Now, where would I find the name of the CSS class that does what I want to do? I'm not even someone who uses CSS on a regular basis. Well, there's a page that has all of the bootstrap CSS classes, and we could search through it. If I'm looking for a class that affects text, I could start to look through classes that use text, and I might notice that these are the colors that come with the theme, a primary color, a secondary color, a success color. I could do some experimentation, and I have, and I discovered that text-success makes the text of any object you give this class the color that is the success color. If I want to use it, I don't need to pay attention to that dot. I could just take the name of the class and pass it to that class argument. So this would be text-success, and I could reuse that in any object whose text I want to be the color of success. It'll be all my card headers. Let's reload the app and see the effect. So now nothing's changed except the color of the text, and it's the success color.

Well, there's a page that has all of the bootstrap CSS classes, and we could search through it. If I'm looking for a class that affects text, I could start to look through classes that use text, and I might notice that these are the colors that come with the theme, a primary color, a secondary color, a success color. I could do some experimentation, and I have, and I discovered that text-success makes the text of any object you give this class the color that is the success color.

The next piece of text that I want to, well, eventually, I'll color this text too, but I'll use a different method to do that. I will use this class method to change the background of the sidebar. I would like the background to be like these backgrounds, and there's a bootstrap class for doing that, and like everything else, the sidebar function takes a class argument. Through experimentation and induction, I figured out that the bootstrap class, CSS class, that would make the background success would be that, but I don't want it to be blue. I want it to be the secondary color. So bgSecondary will change that background color. So the class of this object comes with a styling rule that says its background is this color, the secondary color. It's looking pretty good.

All right. Now I have this text down here. Let's make that blue as well. I looked, and I looked, and I could not find a CSS class that would change this text, but the latest editions of bootstrap have something beyond CSS classes that I could rely on, and those are variables. See what I mean about variables? Let's come over here to the bslib page. Under the theming section, there's an article that lists all the theming variables. All right. Here's some variables. For each of these, there is a CSS class or multiple CSS classes that use these values. So there's some class that says make the text this gray value, but it doesn't say hashtag F8, F9, F8. It says make it gray 100, and then gray 100 is a variable that contains the value to use. If I change the value contained in gray 100, then every class that uses gray 100 will be updated, and so my change will propagate or, dare we say, cascade through my app using this variable system. I think you could kind of imagine how it works if you realize that success, the success color, is a variable, as is primary, secondary, and so on. So when I changed success, I changed all the places that use success to use that blue color.

Now, the question is, are there any variables that affect tables? Well, we could search for variables that affect tables, and one thing I noticed is there's a variable called table color. I wondered what color gets changed. It's probably not the background because there's a variable called table BG, and just by trying it out, I saw that it is the color of the text in the table. So if I want the color of the text and table to be a different value, I'll just set this to a different value. And where do I set that? I set that in the BS theme function, just like when I change the success variable to be a different value. So now this variable will be table dash color. I'm going to say equal to that same blue. Reload the app. And voila, we're starting to get where we want to go.

Applying thematic to plots

Now there's one big hole in the color theming of this app so far, and that's the plots. Now, each of those plots are made with ggplot2, and with ggplot2, you can control everything, and I mean everything about the visual appearance of a plot, but it does take a lot of typing to fix all that to make it look like something custom, and I'd rather skip over that. Luckily, I can. There is an amazingly magical way to make your plots match the rest of the app, and that is with a package called thematic.

So I'll load the package in my app with library thematic, and then thematic comes with a function called thematic underscore shiny. When you add that to your app, every base R plot, every lattice plot, and every ggplot2 plot will automatically inherit the styling of the app that surrounds it. So let's give that a try. I'll reload the app, and you see right away with that one line of code, my plots now look like they belong here in this darkly themed app, and it looks like they're using a set of colors I didn't really mess with, but they look good in the app because that's how darkly themed works. It's a set of things that look good together. All right, now this is a really nicely colored app.

There is an amazingly magical way to make your plots match the rest of the app, and that is with a package called thematic. So I'll load the package in my app with library thematic, and then thematic comes with a function called thematic underscore shiny. When you add that to your app, every base R plot, every lattice plot, and every ggplot2 plot will automatically inherit the styling of the app that surrounds it.

Changing fonts

I want to change the fonts though. There's several ways to change the fonts in an app, but they all rely on variables. So there are three variables that change fonts. One of them is called base font. One of them is called heading font. One of them is called code font. I don't have any code appearing in my app, so I won't be working with code font. And just for discussion right now, let's stick to just one of these. Let's try to change the base font.

To change one of these font variables, we could pass it the name of any website font. This is a font that every browser is going to know and have access to. We could pass a single value here, or it could be a vector of several fonts. So you have backup fonts if for some reason one font doesn't work. I'll reload that app. We should be able to see that some text has become Times New Roman. It has. I definitely don't want to keep that, but I wanted to see that this works. But it's not going to work for me because I want to change the font to something that's not universally known.

For example, I might want to change it to Pacifico, which is a Google font made available by Google. Not too hard to get, but it's not a website font. So if I just ran this, the browser probably wouldn't know what Pacifico is. To pass that information to the browser, I could use the helper font Google. For any Google-based font, use font Google. If your user's browser does not have that font, font Google will download it and cache it for the user so they can use it to see your app. So here's Pacifico. Makes a big difference. It's also not the font we want to use at the end of the day. But the font we do want to use, it's called Lado. It is essentially the default font. Why are we going to use it? Because it's the font that appears in my logo. So this is Lado. It's the same font from the subtitle, Highly Flexible Solutions. It doesn't make a good example today because it is the default font, but this is how we'd swap that out.

Next, let's swap the headings to use the Democo font. That is a different font yet. That font is called Open Sauce Sans. It's not a Google font at all. It's just a font that I actually have the font file for. And if I want to use it, I could give that file to my user's browser and then tell them to use that font. This is a foolproof way for getting any font into your Shiny app. So to give the font to user's browser, I put it in this www folder. And the next I tell the app to use it for the headings. The way I do that is I set heading font to a different helper function. This time is font underscore face. The way font face works is first I give the font a name, Open Sauce Sans, in case I want to refer to it anywhere. And then I have to tell it where that font lives. And this is a little tricky. It's something you might want to look up because you have to use CSS syntax here. And the way the syntax works out is you pass a URL, which is a file path to your font file, assuming the font files at the top level directory of the web page, which is where it will be since I put in www folder. Hopefully I spelled that correctly. But it doesn't in there, you have to tell the CSS what the format of that file is. In our case, the format is true type. That's what the TTF means, true type font.

And I don't see that working. So I either missed something or need to restart R. URL, Open Sauce Sans regular, Open Sauce Sans regular TTF. Format true type. Ah, yeah, I didn't close this. So I had opened a quotation mark around this file path. I didn't close. This time I closed it. And now we see that there's a legitimate change here in the header. It looks like the DemoCo logo. But oddly, these card headers don't follow that new font. You might not notice it, but I notice it. It's weird to me, but the card headers aren't considered header classes. I can make them header classes, and then they would use the header font. And I will do that using the class arguments I already put in here. I can give each of these multiple classes with the same class argument. For example, if I gave it the H1 class, as well as text success, it'd be a H1 header. That's a first level header. That's actually really big. I don't want to do that. I want it to be maybe a sixth level header, H6. That'd make it the same size it is, but it'd technically be a header. So it would inherit the header font as well as inheriting the success text color. Let's give that a try. All right, there we go. We have the fonts here and in all these places. So this is how the finished app looks, minus a couple things, the Git link, some other things, but it is ready to publish.

Publishing the app

Now, if you want to publish a styled app, there's a couple things I suggest you consider doing, and I'm going to do them right now. The first is we should hard code into here the version of Bootstrap that we're using. Bootstrap gets updated from time to time. This is always going to use the latest version of Bootstrap, but we wouldn't want an update to change the way our app looks. We may not like the change. So what we could do is we could find out what version of Bootstrap we're using today with the function version default, version five, and then we could add that as an argument to BS theme. Now, this will always use version five of Bootstrap to recreate the theme that we see here today with our eyes. The second thing I'm going to do is just something I do when I want to share the code for a Shiny app. That's definitely not always the case, but I'm going to add a GitHub banner. I'll refer back to my repo. I could do that really easily with the Git link package. All I have to do is use the function ribbon CSS, which comes in the Git link package. Let's call it inside my page, and it takes the URL that I want to link back to. That's going to be the URL of my GitHub repo. Put it there, and I'll run the app.

Now that I have the app looking the way I want, publishing the app for me is really easy because I'm inside Posit Workbench, and I have a Posit Connect account. Posit Connect can host Shiny apps, and they're linked, so I just click publish. It asks me if I want to publish this app, and I want to publish it under the name finished app. Click publish. It says there's already one there. There is. It's the one I showed you at the start of the webinar. I'm going to replay it. Now it's deploying the finished app. If I close the preview window, we can see things happening down here.

It's uploading everything that Posit Connect is going to need to serve and run this app. It tries to open up the pop-up window. Here we go. We can see that it's already serving the app. While it loads the app, I have controls over here that as the app owner I could use to decide who can see the app. I could limit it to specific users. I'd add them here. They'd be people who have accounts on my Posit Connect. I don't really want to limit it. Anyone on my intranet with Posit Connect can look at this one. I could also choose what URL I want the app to be at. Finished app works just fine for me, so I'll leave that the way it is. Then I can even choose what sort of resources, compute resources, I think this app needs to support it as multiple users visit it. When it's all done, when I'm all done making my selections, I could share this URL with anyone I want to see the app, and it will appear full page like this. So there we go. There's our published, styled, finished Shiny app.

Thank you so much for sticking through this with me and learning so much about how to style your Shiny apps. The take-home messages are use the bslib package. Use its excellent website, which you can find through Google to learn all about the package. You could lay out your apps, create cards, create value cards. You could use classes, CSS classes, to change the appearance of your app. You use CSS variables from the bootstrap package, and you could also use themes from the boot swatch package. If you have any questions, drop it in the comments, and we'll try our best to answer them. Thank you.

Thank you so much, Garrett, for the awesome demo and for sharing the code with all of us. YouTube should automatically push you over into the live Q&A room right now, but if for any reason it doesn't, it will be in the YouTube description below as well, and I'll put it in the chat. We'd love to have you join us for the Q&A, and as a reminder, you can use the YouTube chat or the Slido link on the screen right now for anonymous questions. Also, if you're curious to try Posit Workbench or Posit Connect, which you saw today, I've also included a link on the screen to let us know and chat with our team, too. With that, let's go jump over to the Q&A. See you in a second.