
Carson Sievert: Towards the next generation of Shiny UI
About the talk: Shiny, the R package for creating interactive web graphics, recently celebrated its 10th birthday. Since then, Shiny has grown tremendously in many areas (e.g., performance, functionality, extensions, etc); however, a "hello world" Shiny app still looks like it did 10 years ago. This is mostly because Shiny goes to great lengths to ensure backwards compatibility; and as a result, default Shiny UI will likely continue to be based on Bootstrap 3 (a CSS styling framework released in 2010). However, thanks to the new bslib R package, it is now easy to opt-into a modern Bootstrap 5 foundation that "just works" with Shiny, R Markdown, flexdashboard, pkgdown, bookdown, and more. In addition to upgrading Shiny's Bootstrap dependency, bslib also makes it much easier to do custom theming, leverage modern layout techniques, and create custom components (all from R without any CSS/HTML/JS required). At this point, bslib is still maturing, and does not yet provide what we'd consider a "complete UI toolkit", but it should eventually replace and/or improve upon all of Shiny UI. In this talk, I'll highlight bslib features that we're most excited about (e.g., expandable cards, accordions, (sidebar) layouts, input controls, etc.), discuss some best design practices for improving user experience with these tools, and present some real world examples of these tools in action. Speakers' bio: Carson is a software engineer on the Shiny team at Posit. He joined Posit in 2018, and in recent years, has focused primarily on Shiny for Python and improving Shiny UI. The Shiny UI work has manifested in the creation and development of many R packages such as bslib, thematic, htmltools, htmlwidgets, sass, shiny, rmarkdown, flexdashboard, and more. Carson also has a PhD in statistics, is a recipient of the ASA's Chambers Statistical Software Award, has maintained the R package plotly since 2015, authored the book "Interactive data visualization with R, plotly, and shiny", and ran a successful freelance consulting service for numerous years
image: thumbnail.jpg
Transcript#
This transcript was generated automatically and may contain errors.
Well, I don't know about you, but the benchmark for all these talks has been 100 on 100 for me, and I'm pretty sure the next one is going to get the same score. The next speaker is Carson Sievert, who is a software engineer on the Shiny team at Posit. He joined in 2018, and in these years, he has focused heavily on Shiny for Python and Shiny UI. His work has resulted in many packages that we have grown to love, bslib, htmltools, thematic, sass, Shiny, rmarkdown, flexdashboard, and so many more.
The contributions, the sheer length of that list is incredible. He has also maintained the R package Plotly since 2015 and authored the book Interactive Data Visualization in R, Plotly, and Shiny. I cannot think of anyone better to lead the talk titled Towards the Next Generation of Shiny UI. Carson, cannot wait for you to chart our course towards the future.
Thank you. All right. Well, thanks so much for the introduction. Excited to be here and talk to you on this subject. I just want to mention I have a link here, bit.ly forward slash bslib dash dashboard slides dash slides, if you want to get back to the slide deck.
I'll also be going through like an R coding demo that you'll find a link to a Posit Cloud instance to get back at that R code in a reproducible environment.
And apologies if this talk title is maybe a little too generic and maybe a little bit too profound for what I'm actually going to talk about today. But I didn't really know exactly what I wanted to talk about until this week, and probably a more targeted title would just be how to make a dashboard with bslib.
Overview of bslib
So you might have already heard about the R package bslib, which Shiny now depends on. And you might have already used it to basically upgrade the version of Bootstrap that Shiny uses underneath the hood. So kind of like the first main goal for bslib is to allow you to kind of seamlessly use any version of Bootstrap and easily customize the CSS that comes with Bootstrap.
And we'll see a quick example of this a little bit later on, but kind of the meat of the talk today is going to be about how we've been kind of slowly been thinking about increasing the scope of this bslib package into including sort of like be the new home for building out like a modern UI toolkit that could replace not only Shiny, but Shiny dashboard as well.
And currently this includes at least like about a handful of components, including cards, the development version of bslib now includes some sidebar layout stuff. We also have value boxes and accordions and page and layout helpers and a few other things. And we haven't really started this work for Shiny for Python, but we're kind of talking about integrating this work into Shiny for Python, as well as the Shiny UI editor.
There was actually a release for the Shiny UI editor today that adds integrations for cards. But this is very much in the early stages where bslib is kind of leading the charge in terms of building this stuff out for R, but we're hoping to be able to bring kind of the same world of a UI toolkit to Python and the UI editor.
Page containers and theming
So the easiest way to get started with bslib is to use one of its drop-in replacements for Shiny's page containers. So the main difference here is like a difference in naming that embraces like autocompletes and snake casing. So you start off with creating your page container by saying page underscore, maybe fluid or fixed or nav bar. And this is just a basic fluid page with a level one header.
So page containers currently default to Bootstrap 5, basically because bslib currently defaults to Bootstrap 5, and that's the most modern version of Bootstrap. But we're going to be a lot more aggressive than, say, Shiny would be about always updating to the most recent version of Bootstrap. So if you're using bslib in production, it's probably a good idea to pin the version of Bootstrap to whatever the current version of Bootstrap is, so that when you go to upgrade bslib in the future, you're not surprised by, you know, some Bootstrap-specific features that you are using that happen to break with a more modern version of Bootstrap.
And then just quickly want to mention, you know, with this bstheme object, you have access to boot swatch themes. So Bootstrap 5 came out with at least, like, a few new boot swatch themes. One example would be Quartz. And you may have also seen bslib's ability to do custom theming, where we make it pretty easy for you to just choose, like, the main colors and fonts, but there's also hundreds of different theming variables that are exposed through Bootstrap. And if you've ever worked with, like, custom fonts, you may have noticed this font Google function, which makes custom fonts a lot easier, where it can automatically download cache and import Google font files for you.
And so that's kind of, like, in a nutshell, how to do theming on the page level. But if you want to get more targeted with your theming, where you want, like, say, certain headers or certain components to be styled a little bit differently than others or handle, like, spacing between components and this sort of thing, it can be really helpful to be aware of Bootstrap utility classes. So here I'm adding a class of text center and text primary to this header, so that I get this title centered and a different color for the text.
Coding demo: fillable containers and fill items
So I would just like to jump right into a coding demo. There's this link here if you want to get back to basically the same R script on Posit Cloud. I do want to highlight the fact that this stuff is very much still in development, especially the sidebars and accordions are not yet on CRAN, so you have to install from GitHub to get it. But I'm actually going to be showing you a branch currently that we don't even have on main. So there's a couple, like, naming differences that are still very experimental at this point.
I have this R script here where I'm going to load bslib and Shiny, and then also for sake of demonstration, I'm going to use the R package crosstalk, basically so that I don't have to, like, write a bunch of Shiny code and create a Shiny app for every single example. So all the examples we're going to see here today are actually there's no Shiny server running in the background. This is all basically a static website and any sort of, like, filtering between inputs and outputs is going to be handled purely in the browser via crosstalk.
And then I'm also going to use Plotly and Leaflet just to have some nice visuals to go along with this. So I'm going to load all these packages, and then I'm just going to kind of wave over this setup code here and just kind of mention I'm using crosstalk to create a list of basically, like, drop-down input controls. So just imagine this as, like, an input of, like, select inputs if you're creating a Shiny app.
So I'm going to create these. I'm also going to create a list of Plotly visuals. And all of these filter controls and the plots are going to be using this diamonds dataset. So I'm going to be able to basically, like, drill down on diamond cut, color, and clarity. And then I'm also going to be able to get histograms of, like, the diamond price and the diamond carats, as well as counts by cut and clarity. But the details there are not so important for this.
But I'm also going to use another dataset to also, I'm just going to want to incorporate a map eventually in these examples. So I'm going to create another map just for sake of comparison with the Plotly visuals.
But let's get right into, you know, an example that you, you know, hopefully all of you have experienced creating, like, a fixed page with maybe just one output inside of that fixed page. You may have noticed that, you know, things like Plotly or Leaflet or Shiny's plot output, basically anything that's kind of output-like in Shiny tends to look a little bit like a Shiny, tends to want to default to a height of 400 pixels.
And you might have used something like Shiny's fill page before, which allows you to do something similar to this. It's a little bit more difficult. But bslib is kind of introducing this concept of a fillable container. And a fillable container basically, like, activates what we're calling a fill item. So that the fill item is allowed to grow and shrink with the size of that fillable container.
So because page fillable is basically creating this container that's fillable and setting its size to the browser window, and there's a fill item inside of that fillable container, this plot will no longer just always default to a 400 pixel height. It will use the size of its fillable container to decide how large it needs to be.
It will use the size of its fillable container to decide how large it needs to be.
So now if I put multiple plots in here, inside of this fillable container, these plots are allowed to shrink and grow to fit the size of this fillable container.
So we're going to, like, return to this idea quite a bit. So I just want to make a sticky note here. About this just kind of reminding us of this terminology here. That a fillable container activates a fill item's ability to grow and shrink. And also, we're thinking about kind of outputs in general in a lot of cases, like, most HTML widgets, things like shiny plot outputs. And also a lot of the components that we see here today are going to want to act like fill items by default.
And I'm also going to use this syntax here quite a bit in some of these examples of this triple bang, Rlang's triple bang operator, which this will allow me to basically pass in all of the plots in this list of plots as arguments to this page fillable. So this is equivalent to what we saw up here.
Introducing cards
So introducing cards. If I were to just put a card inside of a page fixed context, it does what you might think it might do, where it's very similar to before, where I just have a plot that's going to default to 400 pixel height. But then the card is going to allow me to put, like, a card header. So I could put a title above the card body area. And then I can use card body to, you know, put some content inside of this card.
Now, if I just change the page fixed to page fillable, again, we're going to think about cards as like, you know, like a fill item, almost like a plot. And since this is a fill item going inside of a fillable container, the card itself is going to be now allowed to determine its size based on whatever the size of the browser window is. So I could even make this very small, and the plot will still do its default 400 pixel height. But the card itself is allowed to determine its size based on the fillable container.
So now, you know, if you instead wanted the plot to always match the size of the card body, you can kind of do the analogous thing at the card body level of saying instead of kind of like a paged fixed context where it's not a fillable container, I can make this a fillable container. And now the fill item inside of this fillable container is activated to be able to grow and shrink to match whatever the size of the card body is.
So now if I wanted to dump multiple plots into this card body, you know, I'm going to there's going to be like a lot of stuff to show now inside of this card body. So if this card body becomes really small, I might want to say at some point, like once this gets to a size that's smaller than 400 pixels, I don't want to allow those plots to keep shrinking so that they're like zero height. So you can do something like I want the minimum height to be 400 pixels. And now if I have contents that are larger than that 400 pixels, I can just scroll and it doesn't shrink to be smaller than that size.
And you might be wondering, like, you know, why do we why are we kind of making this kind of complicated in the sense that at the component level, we're making you think about fillable containers and fill items. And part of the answer is it gives you a lot of power to do things like combine scrolling and fill behavior at a very particular sub region of the UI.
So I'm going to give you an example here where I just have like the typical page fixed context and I have a couple cards here. So since I'm not trying to fill this to the page, they're going to like do their normal thing of rendering 400 pixels by default in terms of the height. But I'm also going to on each one of these cards add this full screen behavior that when I now hover on the card body, I'm going to get this icon that says expand, which is short for expanding to full screen. So I'm going to expand this first card here. And when I do this, the card is now going to match whatever the size of the browser window is.
And similar to how before, since I have a card body here and not a card body fillable container, the plot is just going to render to its default height at 400 pixels and just remain at 400 pixels regardless of how big the card body is. So I can get real small here and then I need to scroll to get at the entire plot. Now, if you contrast this with the card body fillable card here, now if I expand, the plot will grow and shrink to match whatever the card body size is here.
Right, so in a lot of cases, if you're using this full screen feature, you're going to probably want to pair this with a card body fillable context, especially if you're in this kind of scenario where you have like an output visual that you always want to basically match whatever the size of the card body size is.
So I'm going to wrap this up into kind of a helper function and also add this utility class to make the card headers background color to be dark. Then I'm going to wrap each one of these plotly plots up into a plot card. And then I also just want to mention in general, we're kind of thinking about these components as making it easy for you to preview them at the console. So, you know, you might be used to like printing stuff like div or a Chinese column at the console and it just prints out the HTML that would eventually go into the page. But for these things, you probably, you know, in most cases, want to just preview the results. So when you print these things, we'll kind of automatically wrap it up into a page container for you. So now I can preview the fact that I have a plot card here and I can expand it to full screen and the plot will grow and shrink to match whatever the card body size is.
Layout column wrap and sidebars
And then we also have like a new way to create kind of like column centric layouts through this layout column wrap. So kind of think of this in some way as kind of like a modern equivalent to fluid row and column in a way that you just want basically the same column size for each one of your columns. There's a lot more to this layout column wrap than I'm going to cover today. But just to demonstrate here, you can give this width argument a value like one half and that's going to basically give me like a two column layout.
And in the same way that we're thinking about cards and outputs as fill items by default, since this is a fill item, I can put this into a fillable container like page fillable with another fill item like pay plot counts. And now this gives me kind of like a grid like layout where in the first row, I have two columns and in the bottom and the second row, I just have something that spans the full width.
So I'm going to save this result and then feed this into another layout function that we just added in the development version called layout sidebar. And this is also a fill item. So if I plop this into a page fillable context, this is going to make it so that I have this layout sidebar that's always going to grow and shrink with the size of my browser window. So notice I have like the sidebar on the left hand side and the main content on the right hand side. And I can go up and down like this. But this collapse toggle is always going to remain constant here towards the bottom of this layout container where I can toggle the visibility of this sidebar.
And currently in the viewer, we're not supporting CSS transitions in the way that I'm using it here. But in a real browser, you would see a transition happening with the collapsing of the sidebar.
So this is kind of like analogous to the card body introduction to the card body context in the sense that if I think about the main content area here, I've just kind of put in a lot of content into this container. And I'm allowing it to just kind of render to its default height. And then I'm allowed to scroll up and down if there's lots of content. But if I wanted to opt into more of like the card body fillable behavior, I can set this fillable equal to true on layout sidebar. And now it's going to create a fill. It's going to treat the main content area as a fillable container so that the fill items that are going into this container are allowed to activate their ability to grow and shrink.
So this ability for layout sidebar to kind of fit to the page is really nice. But it's not always going to give us a great user experience, especially if I'm in this kind of scenario where maybe I want to add like some more visuals where, you know, these input controls, I didn't really point this out, but I have this ability to go in and change the cut and color and clarity of the diamonds that I'm looking at. And that's going to update all of these diamond specific outputs. But it's not impacting what information is being shown on the map. Like this is an entirely different data set. And these input controls are just not semantically related to this mapping visual.
So one thing I can do is just pull out the map from the layout sidebar container. So at least now there's a better visual grouping of this sidebar layout being more like visually closer to the plotly visuals in the main content area. But now the map and the map here is just kind of separated out towards the bottom. And it's a little bit clearer that these input controls are not related to the map.
So if you're kind of in this situation where you have like input controls that are want to be kind of grouped together in different areas of the page, you can leverage the fact that this layout sidebar integrates nicely with a card. So I can use the same card API and just kind of drop in a layout sidebar similar to how I would with like a card body. And now if I print this out and preview it, I've actually also specified that I actually don't want the sidebar to be open by default. So it's going to be closed. But I can go here to the lower left hand corner and toggle the sidebar to be open. And now I have a filter that's specific to this map all wrapped into a card that I could plop into a larger page.
Navbar pages and shared sidebars
So let's kind of combine these two where I'm going to take this basically the same layout sidebar diamonds example. And I'm going to feed that into a page of a navbar page. And not only that, but I'm going to basically ask this navbar page to make the diamonds page fillable, make a fillable container just for the diamonds page. And then I'm going to have a separate page for the map with the earthquakes data.
Open this here. And, you know, now I have this nice, you know, kind of global sidebar that's only here when I'm on the diamonds page. And on the earthquakes page, the sidebar is no longer relevant. And I've basically scoped these sidebars in this case to this specific card. And on this page, scoped just to this page.
Now, if you want more of like the shiny dashboard kind of thing where it assumes that you want kind of a singular sidebar that's shared across every page, then you can do something like this where you can provide the sidebar argument or sorry, the sidebar object to the sidebar argument. And, you know, if you are in this kind of scenario where you have a lot of sidebars and, you know, if you are in this kind of scenario where you have some input controls that are truly relevant and truly affect the data being shown on each page, then, you know, maybe you want this kind of functionality.
And just to point out, you can also like maybe in this situation, you know, you do want a shared sidebar, but you actually want this to be part of maybe a larger page. You can just change the page nav bar to nav's tab card. And now you have this shared sidebar across multiple pages, but now sitting inside of a card that you could then put into a larger page if you wanted to.
Value boxes and accordions
And then we also pretty quickly here, I think I'm kind of running out of time, but we've also added value boxes in bslib. So, these are pretty similar to what you would get with Shiny dashboard and flexdashboard, but it is a little bit more extensible and flexible. And so, you have this ability to like we basically give you like a showcase slot is what we call it. And you can put any sort of HTML object inside of here. So, you could actually put like HTML widgets and, you know, other stuff. But here I'm using this BS icons package, which gives you icons that look like are designed for Bootstrap. And then also like customized colors and stuff like that.
But if you're interested in like doing more sophisticated stuff like with this, you can just like with cards, say value box full screen equal to true and then have like a little spark line inside of this card that when you expand it becomes more of a full view. And I have a link here to some documentation with some examples of this kind of idea.
We're also adding accordions in the development version. So, here I'm going to use accordions and plop an accordion inside of the sidebar where this can be a really convenient way if you have tons of input controls and there's maybe groups of related input controls. You can group them together and then also maybe hide groups of input controls if they're not quite as important as other ones and allow the user to kind of, you know, be able to navigate a lot of input controls a little bit more effectively.
You can also use accordions in kind of more of a standalone fashion. So, I don't necessarily have to put them inside of a sidebar. I can just have like sections of pros if I wanted. And then I also just wanted to kind of as a side note mention, I recently, this is on my personal GitHub repo, but I created this histo slider package that gives you a slider control that allows you to have like a histogram tied to it. So, I can see on this slider here it's telling me like most of the diamonds are like lower than a carat or a carat and a half. But then if I filter on maybe diamonds that are a carat or more, you can see that, you know, obviously diamonds that have a lot of carats are more expensive. So, you can, you know, filter on like a carat or more and be aware that there aren't a lot of diamonds that are a carat or more, but they're, you know, more expensive.
Summary and closing thoughts
And just to summarize quickly here, so, this toolkit that we're building out in bslib, obviously, it's kind of a steeper learning curve than Shiny Dashboard and flexdashboard, which kind of limits you in a way that is useful, but when you want to like kind of go beyond sort of the templates that it provides you, it's really hard to go beyond that. So, there are benefits to kind of using this more flexible model. It's going to allow you to do more stuff with theming. It's going to allow you to compose stuff a lot better and extend things and port them to other projects.
And you may miss some Shiny Dashboard features. I could get into this, but for sake of time, I don't think I will. But as always, we love to hear your feedback. If you're trying to like port a Shiny Dashboard project to using bslib, I would love to know how that goes and if you have ideas for improvements.
And also, just remind you that not everything really needs to be a dashboard and or use Shiny. So, we've designed these UI components so that you can use them in rmarkdown, use them to create static sites like we saw today. As of today, I don't think you can really use these in a seamless way with Quarto, but hopefully that's coming down the pipeline and that you can kind of use these components to, you know, do more than just create a dashboard. And just as a reminder, this stuff is still in development. So, only try this out if you're, you know, not trying to ship stuff to production. So, but hopefully we'll see a release in the next few weeks.
And also, just remind you that not everything really needs to be a dashboard and or use Shiny.
And that is all I have for today. If you'd like to learn more, go to rstudio.github.io slash bslib and expect some documentation improvements in the coming weeks. But at least we do at the moment, we have, you know, an article on sidebars if you're interested in seeing that. It is on the development version, but at least we have some writing about it and some examples. Again, the link to the cloud project and some links to my personal stuff. And that's all I have. Thank you.
Q&A
Thank you so much, Carson, for that wonderful presentation. And I think the histo slider took me away. It's very, very cool. And it reminded me of, you know, when you're browsing datasets on Kaggle, you actually see something like that, but it's not that interactive. And this is just on another level, absolutely. We have some questions from the audience. So, I'll just walk you through them quickly.
So, from Logan, we have, what about ensuring that all the plots in a fluid row are the same height using the fillable logics? Yeah. So, I would say at least for now, it's probably going to be a better experience to use this layout column wrap. So, it's going to be a little bit painful, and hopefully we do a good job of explaining this kind of thing in the documentation. It's not quite there yet, but we're kind of working on this of, you know, what if you want to use a UI that's not in bslib to do things like layout? There will be ways to do it, but it will be more seamless if you use something like layout column wrap.
And, you know, layout column wrap is kind of optimized for assuming that you have equal width columns. But if you do want columns with differing widths, that is still possible using layout column wrap. So, it's, yeah, it's hard to say, like, layout column wrap is the best way to do it. I would say, like, layout column wrap is going to supersede fluid row and column at this point. But if you do really want this filling behavior, I would say, like, very much try to avoid using, like, fluid row and column.
And we have another question from David who asks, how does layout column wrap and other fillable containers relate to the CSS flexbox? So, a fillable container is a flexbox container. So, if you know flexbox, a fillable container is a flexbox container, and the fill items are basically, like, flex1. Like, it's a flex item with the ability to grow and shrink. So, if you do know flexbox and are comfortable with it, you can, you know, of course, use fillable and fill concepts, but then you can also, like, kind of add inline styles or more CSS to get more particular with the behavior that you want. If, like, the kind of abstraction that we provided that's hopefully a lot simpler than working with flexbox directly doesn't quite fit your use case, then you can just kind of tack on more CSS as you need.
And I think the last question that we have is from David, who asks, can you relate or connect the charts in such a way that they interact with each other? So, for example, if you put an input in one, it kind of reflects on the other visualizations as well. Is something like that available or planned? What would you say on that? Well, yeah, I mean, if I understand the question directly, like, that's kind of what we were showing here today with those drop-down input controls, changing values there was updating the outputs. Yeah. And that was working through crosstalk, and there are certainly limitations to what you can do with just crosstalk. So, when you want to do stuff that's a little bit more sophisticated, then you might need to reach for Shiny.
And, I mean, I will just kind of drop a hint at this point that that's a pretty high-priority item for us is to actually kind of come back and revisit crosstalk and think about it now that we're heading in a direction of being able to run stuff purely in the browser. We might be thinking about, you know, basically revisiting the concept of crosstalk and leveraging something like WebR to power, you know, these kind of connected multiple views in a way that you can still write R code and, like, have your server functions customizing how these views are actually connected together, but in a way that ultimately, like, runs in the browser. So, that's kind of on our roadmap to investigate that a little more.
I think that's good to hear. As long as it's on the roadmap, we'll know. And thank you so much again, Carson, for this wonderful tutorial and talk. And thank you, and we'll see you next year. Yeah, thank you.



