Chapter 13 Advanced UI
The previous chapter showed you the higher-level UI functions that come with Shiny. In this chapter, we’ll take that a couple of steps deeper by learning how our R code gets translated into the HTML that’s ultimately sent to the browser. Armed with that knowledge, you’ll be able to add arbitrary HTML and CSS to your Shiny apps with ease.
If you don’t know HTML, and aren’t keen to learn, you can safely skip this chapter. If you don’t know HTML today but may be inclined to learn it, you should be able to make sense of this chapter and understand the relationship between Shiny UI and HTML. If you are familiar with HTML already, you should find this chapter extremely useful–and you can skip over the sections that introduce HTML and CSS.
- Section 13.2 is a quick but gentle guide to the basics of HTML. It won’t turn you into a web designer, but you’ll at least be able to understand the rest of the chapter.
- Section 13.3 introduces tag objects, and the family of functions that produce them. These functions give us all the flexibility of writing raw HTML, while still retaining the power and syntax of R.
- Section 13.4 demonstrates several ways of incorporating custom CSS into your app (for example, to customize fonts and colors).
13.2 HTML 101
To understand how UI functions in R work, let’s first talk about HTML, in case you’re not familiar with it (or its cousin, XML). If you’ve worked with HTML before, feel free to skip to Section 13.3.
HTML is a markup language for describing web pages. A markup language is just a document format that contains plain text content, plus embedded instructions for annotating, or “marking up”, specific sections of that content. These instructions can control the appearance, layout, and behavior of the text they mark up, and also provide structure to the document.
Here’s a simple snippet of HTML:
</em> markup instructions indicate that the word
really should be displayed with special emphasis (italics):
This time I really mean it!
<em> is an example of a start tag, and
</em> (note the slash character) is an example of an end tag.
13.2.4 Parents and children
In the example above, we have a
<p> tag that contains some text that contains
<a> tag.s We can refer to
<p> as the parent of
<a> as the children of
<p>. And naturally,
<a> are called siblings.
It’s often helpful to think of tags and text as forming a tree structure:
<p> ├── "Learn more about" ├── <strong> │ └── "Shiny" ├── "at" ├── <a href="..."> │ └── "this website" └── "."
Any markup language like HTML, where there are characters that have special meaning, needs to provide a way to “escape” those special characters–that is, to insert a special character into the document without invoking its special meaning.
For example, the
< character in HTML has a special meaning, as it indicates the start of a tag. What if you actually want to insert a
< character into the rendered document–or, let’s say, an entire
In HTML, you start paragraphs with "
" and end them with "".
That doesn’t look as we intended at all! The browser has no way of knowing that we meant the outer
</p> to be interpreted as markup, and the inner
</p> to be interpreted as text.
Instead, we need to escape the inner tags so they become text. The escaped version of
In HTML, you start paragraphs with "<p>" and end them with "</p>".
(Yes, escaped characters look pretty ugly. That’s just how it is.)
Each escaped character in HTML starts with
& and ends with
;. There are lots of valid sequences of characters that go between, but besides
lt (less than) and
gt (greater than), the only one you’re likely to need to know is
& is how you insert a
& character into HTML.
& is mandatory if you don’t want them interpreted as special characters; other characters can be expressed as escape sequences, but it’s generally not necessary. Escaping
& is so common and crucial that every web framework contains a function for doing it (in our case it’s
htmltools::htmlEscape), but as we’ll see in a moment, Shiny will usually do this for you automatically.
13.2.7 HTML tag vocabulary
That concludes our whirlwind tour of HTML syntax. This is all you’ll need to know to follow the discussion on the pages that follow.
The much larger part of learning HTML is getting to know the actual tags that are available to you, what attributes they offer, and how they work with each other. Fortunately, there are scores of excellent, free HTML tutorials and references online; Mozilla’s Introduction to HTML is one example.
13.3 Generating HTML with tag objects
With this background knowledge in place, we can now talk about how to write HTML using R. To do this, we’ll use the htmltools package. The htmltools package started life as a handful of functions in Shiny itself, and was later spun off as a standalone package when its usefulness for other packages – like
htmlwidgets – became evident.
htmltools, we create the same trees of parent tags and child tags/text as in raw HTML, but we do so using R function calls instead of angle brackets. For example, this HTML from an earlier example:
would look like this in R:
This function call returns a tag object; when printed at the console, it displays its raw HTML code, and when included in Shiny UI, its HTML becomes part of the user interface.
Look carefully and you’ll notice:
<p>tag has become a
p()function call, and the end tag is gone. Instead, the end of the
<p>tag is indicated by the function call’s closing parenthesis.
classattributes have become named arguments to
- The text contained within
<p>...</p>has become a string that is passed as an unnamed argument to
Let’s break down each of these bullets further.
13.3.2 Using named arguments to create attributes
When calling a tag function, any named arguments become HTML attributes.
## <a class="btn btn-primary" data-toggle="collapse" href="#collapseExample">Link with href</a>
The preceding example includes some attributes with hyphens in their names. Be sure to quote such names using backticks, or single or double quotes. Quoting is also permitted, but not required, for simple alphanumeric names.
Generally, HTML attribute values should be single-element character vectors, as in the above example. Other simple vector types like integers and logicals will be passed to
Another valid attribute value is
NA. This means that the attribute should be included, but without an attribute value at all:
## <input type="checkbox" checked/>
You can also use
NULL as an attribute value, which means the attribute should be ignored (as if the attribute wasn’t included at all). This is helpful for conditionally including attributes.
## <input type="checkbox"/>
13.3.3 Using unnamed arguments to create children
Tag functions interpret unnamed arguments as children. Like regular HTML tags, each tag object can have zero, one, or more children; and each child can be one of several types of objects:
126.96.36.199 Tag objects
Tag objects can contain other tag objects. Trees of tag objects can be nested as deeply as you like.
188.8.131.52 Plain text
Tag objects can contain plain text, in the form of single-element character vectors.
I like turtles.
Note that character vectors of longer length are not supported. Use the
paste function to collapse such vectors to a single element.
## chr [1:26] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" ...
One important characteristic of plain text is that htmltools assumes you want to treat all characters as plain text, including characters that have special meaning in HTML like
>. As such, any special characters will be automatically escaped:
184.108.40.206 Verbatim HTML
Sometimes, you may have a string that should be interpreted as HTML; similar to plain text, except that special characters like
> should retain their special meaning and be treated as markup. You can tell htmltools that these strings should be used verbatim (not escaped) by wrapping them with
Warning: Be very careful when using the
HTML() function! If the string you pass to it comes from an untrusted source, either directly or indirectly, it could compromise the security of your Shiny app via an extremely common type of security vulnerability known as Cross-Site Scripting (XSS).
For example, you may ask a user of your app to provide their name, store that value in a database, and later display that name to a different user of your app. If the name is wrapped in
HTML(), then the first user could provide a fragment of malicious HTML code in place of their name, which would then be served by Shiny to the second user. While I’m not aware of any such attacks having ever happened with a Shiny app, similar attacks have been carried out against companies as tech-savvy as Facebook, eBay, and Microsoft.
It is safe to use
HTML() if your Shiny app will only ever be run locally for a single user (not deployed on a server for multiple users), or if you are absolutely sure you know where the HTML string came from and that its contents are safe.
While each call to a tag function can have as many unnamed arguments as you want, you can also pack multiple children into a single argument by using a
list(). The following two code snippets will generate identical results:
It can sometimes be handy to use a list when generating tag function calls programmatically. The snippet below uses
lapply to simplify the previous example:
You can use
NULL as a tag child.
NULL children are similar to
NULL attributes; they’re simply ignored, and are only supported to make conditional child items easier to express.
In this example, we use
show_beta_warning to decide whether or not to show a warning; if not, the result of the
if clause will be
Welcome to my Shiny app!
13.4 Customizing with CSS
In the previous section, we talked about HTML, the markup language that specifies the structure and content of the page. Now we’ll talk about Cascading Style Sheets (CSS), the language that specifies the visual style and layout of the page. Similar to our treatment of HTML, we’ll give you an extremely superficial introduction to CSS—just enough to be able to parse the syntax visually, not enough to write your own. Then we’ll talk about the mechanisms available in Shiny for adding your own CSS.
13.4.1 Introduction to CSS
As we saw in the previous sections, we use HTML to create a tree of tags and text. CSS lets us specify directives that control how that tree is rendered; each directive is called a rule.
Here’s some example CSS that includes two rules: one that causes all
<h3> tags (level 3 headings) to turn red and italic, and one that hides all
<div class="alert"> tags in the
The part of the rule that precedes the opening curly brace is the selector; in this case,
h3. The selector indicates which tags this rule applies to.
The parts of the rule inside the curly braces are properties. This particular rule has two properties, each of which is terminated by a semicolon.
220.127.116.11 CSS selectors
The particular selector we used in these cases (
footer div.alert) are very simple, but selectors can be quite complex. You can select tags that match a specific
class, select tags based on their parents, select tags based on whether they have sibling tags. You can combine such critieria together using “and”, “or”, and “not” semantics.
Here are some extremely common selector patterns:
.foo- All tags whose
#bar- The tag whose
div#content p:not(#intro)- All
<p>tags inside the
content, except the
A full introduction to CSS selectors is outside the scope of this chapter, but if you’re interested in learning more, the Mozilla tutorial on CSS selectors is a good place to start.
18.104.22.168 CSS properties
The syntax of CSS properties is very simple and intuitive. What’s challenging about CSS properties is that there are so darn many of them. There are dozens upon dozens of properties that control typography, margin and padding, word wrapping and hyphenation, sizing and positioning, borders and shadows, scrolling and overflow, animation and 3D transforms… the list goes on and on.
Here are some examples of common properties:
font-family: Open Sans, Helvetica, sans-serif;Display text using the Open Sans typeface, if it’s available; if not, fall back first to Helvetica, and then to the browser’s default sans serif font.
font-size: 14pt;Set the font size to 14 point.
width: 100%; height: 400px;Set the width to 100% of the tag’s container, and the height to a fixed 400 pixels.
max-width: 800px;Don’t let the tag grow beyond 800 pixels wide.
Most of the effort in mastering CSS is in knowing what properties are available to you, and understanding when and how to use them. Again, the rest of this large topic is outside of our scope, but you can learn more from Mozilla’s CSS resources page.
13.4.2 Including custom CSS in Shiny
Shiny gives you several options for adding custom CSS into your apps. Which method you choose will depend on the amount and complexity of your CSS.
22.214.171.124 Standalone CSS file with
The second method is to write a standalone
.css file, and use the
includeCSS function to add it to your UI. If you’re writing more than a couple of lines of CSS, this gives you the benefit of being able to use a text editor that knows how to interpret CSS. Most text editors, including RStudio IDE, will be able to provide syntax highlighting and autocompletion when editing
For example, if you have a
custom.css file sitting next to your
app.R, you can do something like this:
includeCSS call will return a
<style> tag, whose body is the content of
If you wish for this CSS to be hoisted into the page’s
<head> tag, you can call
tags$head(includeCSS("custom.css")), though in practice it doesn’t make a lot of difference either way.
<link> tags, that need to be pasted into the special
<head> section of the webpage.
For example, the
sliderInput to your Shiny app, you’d also have to make a separate declaration somewhere in your UI to load the .js and .css files for ionRangeSlider. And you’d have to make sure that you only make that declaration once per page, lest you accidentally load a library twice, which can cause some libraries to stop working.
For Shiny, we wanted instead to have R users simply call
sliderInput(), and let us sort out the necessary dependencies automatically. In fact, we want R users to be able to combine whatever UI objects they want, and just not think about dependencies at all.
To make this possible, we created a first-class object for HTML dependencies.
sliderInput(). If you’re such a package author, you absolutely should be using HTML dependency objects rather than calling
13.5.1 Creating HTML dependency objects
To form such an object, you call
htmlDependency with the following arguments:
name: The name of the library.
version: The version number of the library. (If two or more HTML dependency objects are found with the same name but different version numbers, only the one with the latest version number will be loaded.)
system.file(package="pkgname")) if you provide an additional
stylesheet: Relative paths to CSS files that should be loaded.
Here’s an example from the leaflet.mapboxgl package:
In this example, the leaflet.mapboxgl package has a
node_modules/mapbox-gl/dist subdirectory that contains two files:
For this particular library, we could also have pointed to these hosted URLs:
We can do this by passing
src=c(href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/") and removing the
package argument. However, be aware that a small but significant number of Shiny users do so from networks that are disconnected from the wider Internet for security reasons, and for those users, this dependency would fail to load.
13.5.2 Using HTML dependency objects
Once you’ve created an HTML dependency object, using it is straightforward.
If you have a function that returns a tag object, have that function return a tag object and dependency instead, using a
You can bundle any number of dependencies with your HTML this way; just add additional dependency arguments to the