Customizing distill with {htmltools} and CSS

tutorial
distill
R

How I added lots of little features to my distill site with the {htmltools} package, CSS, and a handful of little R functions.

Author

John Paul Helveston

Published

2021-03-25

One of the things I love about {distill} as a site builder is that it is super light weight. It comes out of the box with very few bells and whistles, enabling you to create a site from scratch in minutes. I tried using {blogdown} with the Hugo Academic theme, but in the end I found the overall configuration a bit overwhelming, even with the guidance of Alison Hill’s incredible post on how to do it (btw, if you want to make a blogdown site, you totally should read her posts on blogdown). Distill was just simpler, so I dove in.

That said, once I did get my distill site running, I found myself longing for some of the really cool features I’ve seen on peoples’ blogdown sites, like Alison Hill’s site (if you can’t tell, Alison’s work has been a major source of inspiration for me). But then I realized, “wait a minute…I’m working in R, and whenever I want some functionality that doesn’t yet exist, I can just write my own functions!”

So that’s what I set out to do - write a bunch of functions and hack away at CSS to construct the features I wanted. This post walks through my general strategy and then shows how I implemented some of the features on my site.

Full disclosure: I am sure there are probably other (likely better) ways to do some of these things, but this is what I came up with and it worked for me!

Finding the html

For every feature I wanted to add, my starting point was trying to find an example somewhere of the raw html for that feature. My knowledge of html is very limited and hacky, but I do know that if I see something I want, I can use the “inspect” tool in Chrome to grab the html by right-clicking on it and selecting “Copy element”, like this:

With some html in hand, I had a template to work with. My starting point was always to just drop the html directly into a page and edit it until it looked the way I wanted. But most of the time I needed to replicate and reuse that html in multiple places, so I had to find a way to write R code to generate html.

{htmltools} to the rescue!

Luckily, some clever folks wrote a package that generates html code! Since html controls formatting by wrapping content inside tags, the {htmltools} package uses a bunch of handy functions to generate those tags for you. For example, if I wanted to make a level 1 header tag, I could use the h1() function:

library(htmltools)

content <- h1("Hello World")
print(content)
#> <h1>Hello World</h1>

For most situations, this works great, but there are also times where I need a tag that isn’t yet supported. In those case, you can insert the tags yourself as a string and use the tag() function to create custom tags. For example, the <aside> tag is used in distill to put content in the sidebar, but {htmltools} does not have an aside() function. Instead, I can create those tages like this:

content <- tag("aside", "Hello World")
print(content)
#> <aside>Hello World</aside>

With this in mind, we now have just about everything we need to start writing functions to construct some html! I’ll start with a simple example of writing a function to insert some text in the sidebar.

Getting organized

Before I started writing functions, I needed to find a convenient place to put them so I could use them later in my distill articles and posts. Following the typical folder structure for R packages, I decided to make a folder called “R” in the root directory of my distill site and put a file called functions.R in it. Now I can access any functions I write inside this file by calling the following at the top of any .Rmd file:

source(file.path("R", "functions.R"))

It’s kind of like calling library(package) at the top of a file, except your functions don’t live in a package. Eventually, I may choose to move some of my functions to an external package so others can use them, but for now they’ll live happily in my functions.R file 😄.

Haiku research summaries

Inspired by Andrew Heiss’s research page, I wanted to insert a haiku summary next to each citation of each paper on my publications page. All you need to do is wrap some text in <aside> tags and it will show up in the side bar. But rather than write the html for each haiku (e.g. <aside>haiku text</aside>), I decided to write a simple function to generate the html tags for me.

I started with three functions to generate the tags for some center-aligned text in the sidebar:

# Generates <aside>text</aside>
aside <- function(text) {
  return(htmltools::tag("aside", list(text)))
}

# Generates <center>text</center>
center <- function(text) {
  return(htmltools::tag("center", list(text)))
}

# Generates <aside><center>text</center></aside>
aside_center <- function(text) {
  return(aside(center(list(text))))
}

Now I can insert some center-aligned text in the sidebar with the function aside_center(text). But since haikus have a particular 5-7-5 syllabic structure, I thought it would be better to put each line on a separate row. I also wanted the haikus to be in italic font. So I wrote a haiku() function that takes three text inputs and generates the html to put them in the side bar on separate lines:

haiku <- function(one, two, three) {
  return(aside_center(list(
    htmltools::em(
      one, htmltools::br(),
      two, htmltools::br(),
      three)
  )))
}

With this little function, I can insert haikus throughout my publications page without having to write any html! For example, the html for the haiku for our recent paper in Environmental Research Letters is generated like this:

html <- haiku(
  "A five minute ride",
  "In an EV can increase",
  "The chance you'll buy one"
)

print(html)
#> <aside>
#>   <center>
#>     <em>
#>       A five minute ride
#>       <br/>
#>       In an EV can increase
#>       <br/>
#>       The chance you'll buy one
#>     </em>
#>   </center>
#> </aside>

Important caveat: For this to work, I had to insert each haiku using in-line R code, like this: `r haiku("one", "two", "three")`. If I used a code chunk, the output will get wrapped in a <div>, nullifying the <aside> tags.

Hopefully this example gives you the gist of the general strategy of writing a function to produce the desired html. For the most part, the strategy is the same for all the other features on this post, with the exception that some require a little CSS sprinkled on top.

Float an image left / right with wrapped text

A very common layout I see on lots of sites is an image floated to the left or right with text wrapping around it. Here’s an example from my lab page:

There are probably lots of ways to do this, but a simple enough solution is to use the ::: notation to create custom divs. This isn’t needed if your output is a html_document, but for distill articles you need to create a new div that includes the image and text wrapping around it (see this issue for details as to why you have to do this).

Float a single image

If you have just a single image that you want to wrap text around, you can do it like this:

:::float-image

```{r out.width='150px', out.extra='style="float:left; padding:10px"', echo=FALSE}
knitr::include_graphics("path/to/image")
```

Here is some text you want to wrap around the image.
:::

You can name the div whatever you want - I just used float-image to be descriptive. I included all the CSS needed to float the image in the code chunk settings: out.width='150px', out.extra='style="float:left; padding:10px"'. You may want to adjust the padding to fit your site’s look and feel, but this should be all you need to get the job done.

Float multiple images

Since I use this layout frequently, I decided to define two classes, float-left and float-right, in my jhelvy.css theme that style any images in a div with those classes to float left or right, with a little padding:

.float-left img {
    float:left;
    padding: 5px 10px 5px 0px;
}

.float-right img {
    float:right;
    padding: 5px 0px 5px 10px;
}

Now to float an image and wrap text around it, all I need to do is use one of those classes for the div name, and any images between the ::: marks will be floated left or right:

:::float-left

```{r, out.width='150px'}
knitr::include_graphics("path/to/image")
```

Here is some text you want to wrap around the image.
:::

You can use whatever method you want to insert images, like knitr::include_graphics() or just insert direct html (which is what I actually end up doing most often).

Caveat: Anything in the div created by ::: will be masked to the table of contents, so I don’t recomment wrapping a whole article inside ::: to float multiple images (though you could) and instead recommend wrapping just the elements you want to float.

Final thoughts

Coming up with little solutions to each of these features was a highly iterative process, and for the most part I really wasn’t sure how to do any of this when I first got started. Each feature I added usually started by being inspired from someone else’s work, like the haiku research summaries and buttons with icons and text on Andrew Heiss’s research page. I found the process of coming up with a solution to implement each feature to be a fun way to learn new R tricks, especially in working with CSS. If you’re an R / distill / blogdown user looking to customize your site, hopefully these little examples will inspire you too!

Back to top