HTML customization

library(tinytable)
options(tinytable_tt_digits = 3)
x <- mtcars[1:4, 1:5]

tinytable subclasses

The tinytable package comes with built-in CSS subclasses that provide Bootstrap-style functionality without requiring the Bootstrap framework. These subclasses can be applied using theme_html() with the class argument.

The available subclasses can be viewed by calling cat(tinytable:::get_css()). Here are the main options:

Small padding (tinytable-sm)

tt(x) |> theme_html(class = "tinytable tinytable-sm")
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

Bordered table (tinytable-bordered)

tt(x) |> theme_html(class = "tinytable tinytable-bordered")
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

Striped rows (tinytable-striped)

tt(x) |> theme_html(class = "tinytable tinytable-striped")
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

Hover effects (tinytable-hover)

tt(x) |> theme_html(class = "tinytable tinytable-hover")
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

Multiple classes combined

tt(x) |> theme_html(class = "tinytable tinytable-sm tinytable-hover tinytable-bordered")
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

CSS: cells

The style_tt() function allows us to declare CSS properties and values for individual cells, columns, or rows of a table. For example, if we want to make the first column bold, we could do:

tt(x) |>
  theme_html(j = 1, css = "font-weight: bold; color: red;")
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

CSS: table

The default HTML class for tinytable is .tinytable. We can use this class to customize the overall appearance of the table using CSS. The default CSS style sheet can be viewed here:

https://github.com/vincentarelbundock/tinytable/blob/main/inst/tinytable.css

For extensive customization, users can modify this file, read it as a string in R, and supply it to the css_rule argument of the theme_html() function. Alternatively, they can write their own completely customized CSS rules, as illustrated below.

Notes:

  1. Using css_rule will completely overwrite the default tinytable CSS styling. You can retrieve the original with tinytable:::get_css() as a string, which you can manipulate and combine as required.
  2. When creating a completely new stylesheet, it is useful to assign a custom class name to the table with the class argument in theme_html(). Otherwise styles may spill over to other tables in the same document.
css_rule <- "
.gradienttable {
  background: linear-gradient(45deg, #EA8D8D, #A890FE);
  width: 600px;
  border-collapse: collapse;
  overflow: hidden;
  box-shadow: 0 0 20px rgba(0,0,0,0.1);
}

.gradienttable th,
.gradienttable td {
  padding: 5px;
  background-color: rgba(255,255,255,0.2);
  color: #fff;
}

.gradienttable tbody tr:hover {
  background-color: rgba(255,255,255,0.3);
}

.gradienttable tbody td:hover:before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -9999px;
  bottom: -9999px;
  background-color: rgba(255,255,255,0.2);
  z-index: -1;
}

.gradienttable thead th {
    border-bottom: none;
}
"

tt(x, width = 2 / 3) |>
  theme_empty() |>
  theme_html(css_rule = css_rule, class = "gradienttable") |>
  style_tt(j = 1:5, align = "ccccc")
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

And here’s another example:

css <- "
.squirreltable {
  background-size: cover;
  background-position: center;
  background-image: url('https://user-images.githubusercontent.com/987057/82732352-b9aabf00-9cda-11ea-92a6-26750cf097d0.png');
  --bs-table-bg: transparent;
}
.squirreltable thead th {
    border-bottom: none;
}
"

tt(mtcars[1:10, 1:8]) |>
  theme_empty() |>
  theme_html(css_rule = css, class = "squirreltable")
mpg cyl disp hp drat wt qsec vs
21 6 160 110 3.9 2.62 16.5 0
21 6 160 110 3.9 2.88 17 0
22.8 4 108 93 3.85 2.32 18.6 1
21.4 6 258 110 3.08 3.21 19.4 1
18.7 8 360 175 3.15 3.44 17 0
18.1 6 225 105 2.76 3.46 20.2 1
14.3 8 360 245 3.21 3.57 15.8 0
24.4 4 147 62 3.69 3.19 20 1
22.8 4 141 95 3.92 3.15 22.9 1
19.2 6 168 123 3.92 3.44 18.3 1

And yet another one. Some Rmarkdown documents like bookdown use older versions of Bootstrap that do not have a caption-top class. We can recreate that functionality with CSS rules and classes. For example,

rule <- ".bottomcaption {caption-side: bottom;}"
tt(head(iris), caption = "Hello world") |>
  theme_html(class = "bottomcaption", css_rule = rule)

Bootstrap

The Bootstrap framework provides a number of built-in themes to style tables, using “classes.” To use them, call theme_html() to set engine="bootstrap", and then use the class argument. A list of available Bootstrap classes can be found here: https://getbootstrap.com/docs/5.3/content/tables/

For these examples we set a global option that calls theme_empty() in order to remove the top, mid, and bottom rules of the table. This is optional, but helps illustrate the basic Bootstrap styling. We also override some default CSS rules set by tinytable, using the css_rule argument. We also set the default css_rule to an empty string in order to override the default tinytable CSS rules.

options(tinytable_tt_theme = theme_empty)
options(tinytable_html_css_rule = "")

tt(x) |> theme_html(engine = "bootstrap", class = "table")
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

To produce a “bordered” table, we use the table-bordered class:

tt(x) |> theme_html(engine = "bootstrap", class = "table table-bordered")
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

We can also combine several Bootstrap classes. Here, we get a table with the “hover” feature:

tt(x) |> theme_html(engine = "bootstrap", class = "table table-hover")
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

By default, Bootstrap 5 places captions at the bottom of the table. This can be changed by using a different class:

tt(x, caption = "A caption on top") |>
  theme_html(engine = "bootstrap", class = "table caption-top")
A caption on top
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08
Warning

Quarto ships with its own set of Bootstrap CSS rules, which may sometimes interfere with tinytable styles. It is somewhat “safer” to work with the default and more vanilla tinytable class rather than set engine = "bootstrap" Also, Quarto does not appear to support all bootstrap subclasses, like table caption-top.

options(tinytable_tt_theme = NULL)
options(tinytable_html_css_rule = NULL)

Fontawesome

We can use the fontawesome package to include fancy icons in HTML tables:

library(fontawesome)
library(tinytable)
tmp <- mtcars[1:4, 1:4]
tmp[1, 1] <- paste(fa("r-project"), "for statistics")
tt(tmp)
mpg cyl disp hp
for statistics 6 160 110
21 6 160 110
22.8 4 108 93
21.4 6 258 110

JS Plotting Libraries

This section shows how to insert plots from JavaScript plotting libraries like Plotly, D3, etc.

Plotly is a popular JavaScript library for creating interactive charts and visualizations. We can embed Plotly plots directly in tinytable HTML tables by combining two features:

  1. Loading the Plotly library: Use theme_html(script = ...) to load the Plotly JavaScript library from a CDN
  2. Embedding plot code in cells: Insert raw HTML and JavaScript code directly into table cells

This approach works with any JavaScript library - Plotly is just one example. The script parameter in theme_html() injects custom JavaScript into the HTML output, making external libraries available to code embedded in table cells.

Here’s a complete example showing histograms, density plots, and line plots (sparklines) embedded in a table:

# Prepare data
plot_data <- list(mtcars$mpg, mtcars$hp, mtcars$qsec)

# Random data for line plots (sparklines)
lines <- lapply(1:3, \(x) data.frame(x = 1:10, y = rnorm(10)))

# Okabe-Ito color palette (colorblind-friendly)
okabe_ito <- palette.colors(palette = "Okabe-Ito")

# Helper function to create inline Plotly code for a histogram
create_histogram <- function(data, color = okabe_ito[2]) {
  id <- paste0("plot_", sprintf("%08x", as.integer(runif(1) * 1e8)))
  data_json <- paste0("[", paste(data, collapse = ","), "]")
  sprintf('<div id="%s"></div>
<script>
  Plotly.newPlot("%s", [{
    x: %s,
    type: "histogram",
    marker: {color: "%s"}
  }], {
    showlegend: false,
    margin: {l: 0, r: 0, t: 0, b: 0},
    xaxis: {visible: false},
    yaxis: {visible: false},
    height: 50,
    width: 150
  }, {displayModeBar: false});
</script>', id, id, data_json, color)
}

# Helper function to create inline Plotly code for a density plot
create_density <- function(data, color = okabe_ito[4]) {
  id <- paste0("plot_", sprintf("%08x", as.integer(runif(1) * 1e8)))
  # Compute density using R
  dens <- density(data, n = 100)
  x_json <- paste0("[", paste(round(dens$x, 3), collapse = ","), "]")
  y_json <- paste0("[", paste(round(dens$y, 5), collapse = ","), "]")
  # Convert hex to rgba with 0.3 opacity
  rgb_vals <- col2rgb(color)
  fillcolor <- sprintf("rgba(%d, %d, %d, 0.3)", rgb_vals[1], rgb_vals[2], rgb_vals[3])
  sprintf('<div id="%s"></div>
<script>
  Plotly.newPlot("%s", [{
    x: %s,
    y: %s,
    type: "scatter",
    mode: "lines",
    fill: "tozeroy",
    line: {color: "%s", width: 1.5},
    fillcolor: "%s"
  }], {
    showlegend: false,
    margin: {l: 0, r: 0, t: 0, b: 0},
    xaxis: {visible: false},
    yaxis: {visible: false},
    height: 50,
    width: 150
  }, {displayModeBar: false});
</script>', id, id, x_json, y_json, color, fillcolor)
}

# Helper function to create inline Plotly code for a line plot
create_line <- function(data, color = okabe_ito[6]) {
  id <- paste0("plot_", sprintf("%08x", as.integer(runif(1) * 1e8)))
  x_json <- paste0("[", paste(data$x, collapse = ","), "]")
  y_json <- paste0("[", paste(data$y, collapse = ","), "]")
  sprintf('<div id="%s"></div>
<script>
  Plotly.newPlot("%s", [{
    x: %s,
    y: %s,
    type: "scatter",
    mode: "lines",
    line: {color: "%s"}
  }], {
    showlegend: false,
    margin: {l: 0, r: 0, t: 0, b: 0},
    xaxis: {visible: false},
    yaxis: {visible: false},
    height: 50,
    width: 150
  }, {displayModeBar: false});
</script>', id, id, x_json, y_json, color)
}

# Create data frame with inline Plotly code in each cell
dat <- data.frame(
  Variables = c("mpg", "hp", "qsec"),
  Histogram = c(
    create_histogram(plot_data[[1]]),
    create_histogram(plot_data[[2]]),
    create_histogram(plot_data[[3]])
  ),
  Density = c(
    create_density(plot_data[[1]]),
    create_density(plot_data[[2]]),
    create_density(plot_data[[3]])
  ),
  Line = c(
    create_line(lines[[1]]),
    create_line(lines[[2]]),
    create_line(lines[[3]])
  )
)

# Create table - only load Plotly library via theme_html(script = ...)
tt(dat, width = 1) |>
  theme_html(script = '<script src="https://cdn.plot.ly/plotly-3.1.0.min.js" charset="utf-8"></script>') |>
  style_tt(j = 2:4, align = "c")
Variables Histogram Density Line
mpg
hp
qsec

The key pattern here is:

  1. Load the external library once using theme_html(script = "<script src='...'></script>")
  2. Create helper functions that generate HTML/JavaScript code for each plot
  3. Use these functions to populate table cells with raw HTML/JavaScript
  4. Each plot gets a unique ID so multiple plots can coexist in the same table

This approach works for any JavaScript visualization library (D3.js, Chart.js, etc.) or custom JavaScript code you want to include in your tables.

Miscellaneous