News in htmlTable 2.0

[This article was first published on R – G-Forge, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

A short intro to the new features in htmlTable 2.0. The image is a blend based on a CC image by Ken Xu.

The htmlTable 2.0 package was just released on CRAN! It is my most downloaded package with 160 000+ downloads/month and this update is something that I have been wanting to do for a long time. For those of you that never encountered htmlTable it is a package that takes a matrix/data.frame and outputs a nicely formatted HTML table. When I created the package there weren’t that many alternatives and knitr was this new thing that everyone was excited about, magrittr with its ubiquitous %>% pipe had not even entered the scene. The current update should make it easier to streamline table look, separate layout from content and use tidyverse functionality.

Style and theming

The biggest change in how to use the htmlTable is that you have a separate function for adding style to the table. The change is implemented in a non-breaking fashion, i.e. all the old options should work just as before but the new API is encouraged as it simplifies a lot. The new API changes so that instead of all the css.* arguments we now have a separate function that applies these, addHtmlTableStyle and passes them on as an attribute to the htmlTable:

options(table_counter = TRUE)

  `Group A` = c(20, 5, 380, 95),
  `1` = c(11, 55, 9, 45),
  `2` = c(11, 55, 9, 45)
) %>%
  addHtmlTableStyle(css.rgroup = "font-style: italic",
                    css.header = "font-weight: normal") %>% 
  htmlTable(header = rep(c("No", "%"), times = 2),
            n.cgroup = list(c(2), c(2, 2)),
            cgroup = list('Super', c("First", "Second")),
            rgroup = c("", "Group B"),
            n.rgroup = 1,
            caption = "A simple htmlTable example with the core components")

As we usually want the same layout for the entire table we can accomplish the same effect for all of our tables using setHtmlTableTheme and the style will be applied to all your tables.

setHtmlTableTheme(css.rgroup = "font-style: italic",
                  css.header = "font-weight: normal",
                  pos.caption = "bottom")

  `Group A` = c(20, 5, 380, 95),
  `1` = c(11, 55, 9, 45),
  `2` = c(11, 55, 9, 45)
) %>%
  htmlTable(header = rep(c("No", "%"), times = 2),
            n.cgroup = list(c(2), c(2, 2)),
            cgroup = list('Super', c("First", "Second")),
            rgroup = c("", "Group B"),
            n.rgroup = 1,
            caption = glue("Same as Table {last} but with general styling and caption positioned at the bottom.",
                           last = tblNoLast()))

There is an option for selecting themes with predefined layouts. In addition to the standard which has the traditional htmlTable look, you also have Google docs and blank. The Google docs is still a work in progress and any help making it as compatible as possible with Google’s Drive document when copy-pasting is much appreciated.

Using tidyverse syntax in tidyHtmlTable

In 2017, Stephen Gragg added the tidyHtmlTable to the package. Since then advances to RStudio has impacted in how we use R and it became obvious that the function should instead of strings as arguments directly accept column names just as defined by tidyselect. The tidyHtmlTable solves one of htmlTable‘s greates weaknesses, the need for calculating the rgroup, tspanner, cgroup arguments and using it with the tidyselect interface is now pure joy:

  ~ rowname, ~ `No (First)`, ~ `% (First)`, ~ `No (Second)`, ~ `% (Second)`,
  "Group A",             20,             5,             380,             95,
  "Group B1",            11,            55,               9,             45,
  "Group B2",            11,            55,               9,             45,
) %>%
  pivot_longer(cols = c(starts_with("No"), starts_with("%"))) %>% 
  mutate(group = str_replace(rowname, "Group ([AB]).*", "\\1"),
         rowname = str_replace(rowname, "Group ([AB12]+).*", "\\1"),
         group = if_else(group == rowname, "", group),
         header = str_replace(name, "([^ ]+).*", "\\1"),
         cgroup = str_replace(name, ".*\\(([^)]+)\\)$", "\\1")) %>% 
  tidyHtmlTable(rgroup = group,
                header = header,
                cgroup = cgroup,
                rnames = rowname,
                caption = "A version of the first tables but using the tidyHtmlTable")

A more advanced example on how tidyHtmlTable works with tidyverse we can have a look at the example in the vignette("tidyHtmlTable"):

mtcars %>%
  as_tibble(rownames = "rnames") %>% 
  pivot_longer(names_to = "per_metric", 
               cols = c(hp, mpg, qsec)) %>%
  group_by(cyl, gear, per_metric) %>% 
  summarise(Mean = round(mean(value), 1),
            SD = round(sd(value), 1),
            Min = round(min(value), 1),
            Max = round(max(value), 1),
            .groups = 'drop') %>%
  pivot_longer(names_to = "summary_stat", 
               cols = c(Mean, SD, Min, Max)) %>% 
  ungroup() %>% 
  mutate(gear = paste(gear, "Gears"),
         cyl = paste(cyl, "Cylinders")) %>% 
  arrange(per_metric, summary_stat) %>% 
  addHtmlTableStyle(align = "r") %>% 
  tidyHtmlTable(header = gear,
                cgroup = cyl,
                rnames = summary_stat,
                rgroup = per_metric,
                caption = "A full example of how to apply the tidyverse workflow to generate a table")

When using tidyHtmlTable you can decouple it from htmlTable and provide any table function by supplying the table_fn function.


In htmlTable 2.0 there are plenty of options that have been added. Most of them should start with the prefix “htmlTable.”, e.g “htmlTable.css.tspanner.sep”. While the prefix is useful for reducing the risk of conflicting options between packages, options such as “table_counter” are unchanged in order to avoid unnecessary breaking changes.

NEWS for 2.0

  • Added theming and styling with addHtmlTableStyle and setHtmlTableTheme to reduce the cognitive burden of finding the right option within the docs. Note: this may impact your current tables and hence the major version (2.0.0).
  • Changed so that css.cell is properly applied to rownames, cell fillers and the actual cells of interest (may impact the final layout!)
  • Breaking change tidyHtmlTable: Moved to a fully tidyverse compatible system with tidyHtmlTable. This is a breaking change to the API as we switch from columns as strings to tidyselect syntax and as gather/spread have been replaced by pivot_longer/pivot_wider the default values have been updated in accordance with their defaults, e.g. rnames = "name" and value = "value".
  • Breaking change tidyHtmlTable: Sorting of rows is skipped as we may have situations with repeating inputs and this can easily be performed pre-function by calling dplyr::arrange. This has furthermore the desirable feature that any custom sorting is retained.
  • Added mso-number-format to help (Issue #63) – thanks Rasmus Hertzum
  • txtRound can now add txtInt when formatting the integer section for easier readability
  • Added htmlTable css options – they should all start with htmlTable.
  • pos.caption now uses match.arg as expected
  • Fixed proper S3 function definition for htmlTable with all the arguments
  • Added htmlTable.css.border style option for allowing to choose border style. Also fixed bug with cgroup empty cells and vertical border.
  • Added htmlTable.pretty_indentation option for skipping the stripping of all the tabs that was required due to old Pandoc bug.
  • Added attr(x, "html") <- TRUE by default and UTF-8 encoding on all outputted strings to mimic the htmltools::HTML function behavior.
  • For simple tibble output the tidyHtmlTable can now be used to choose a column for the rnames argument
  • The print statement now respects the chunk_output_type in Rmd files in RStudio

Flattr this!

To leave a comment for the author, please follow the link and comment on their blog: R – G-Forge. offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)