In this code‑through, we’ll learn how to create interactive,
sortable, searchable tables using the {reactable}
package. We’ll start simple and layer on features commonly needed in
evaluation reports and dashboards: formatting, conditional highlighting,
mini in‑cell visuals, filters, and themes.
Why
reactable? Unlike static tables,reactablelets your audience explore data directly in your knitted HTML without needing Shiny.
library(reactable)
library(reactablefmtr) # optional helpers like data_bars(), color_scales()
library(tidyverse)
library(scales)
library(tibble)
mtcars (swap with your own)We’ll use mtcars for a reproducible demo and make row
names a column for readability. You can replace this with
any dataframe (e.g., program metrics, survey items,
player stats).
data <- mtcars %>%
rownames_to_column("model") %>%
mutate(
cyl = factor(cyl),
am = factor(am, labels = c("Automatic", "Manual")),
# create a couple of demo variables for formatting
mpg_rank = min_rank(desc(mpg)),
hp_pct = rescale(hp, to = c(0, 1))
)
dplyr::glimpse(data)
## Rows: 32
## Columns: 14
## $ model <chr> "Mazda RX4", "Mazda RX4 Wag", "Datsun 710", "Hornet 4 Drive",…
## $ mpg <dbl> 21.0, 21.0, 22.8, 21.4, 18.7, 18.1, 14.3, 24.4, 22.8, 19.2, 1…
## $ cyl <fct> 6, 6, 4, 6, 8, 6, 8, 4, 4, 6, 6, 8, 8, 8, 8, 8, 8, 4, 4, 4, 4…
## $ disp <dbl> 160.0, 160.0, 108.0, 258.0, 360.0, 225.0, 360.0, 146.7, 140.8…
## $ hp <dbl> 110, 110, 93, 110, 175, 105, 245, 62, 95, 123, 123, 180, 180,…
## $ drat <dbl> 3.90, 3.90, 3.85, 3.08, 3.15, 2.76, 3.21, 3.69, 3.92, 3.92, 3…
## $ wt <dbl> 2.620, 2.875, 2.320, 3.215, 3.440, 3.460, 3.570, 3.190, 3.150…
## $ qsec <dbl> 16.46, 17.02, 18.61, 19.44, 17.02, 20.22, 15.84, 20.00, 22.90…
## $ vs <dbl> 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1…
## $ am <fct> Manual, Manual, Manual, Automatic, Automatic, Automatic, Auto…
## $ gear <dbl> 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 4, 3…
## $ carb <dbl> 4, 4, 1, 1, 2, 1, 4, 2, 2, 4, 4, 3, 3, 3, 4, 4, 4, 1, 2, 1, 1…
## $ mpg_rank <int> 13, 13, 8, 11, 18, 19, 29, 7, 8, 16, 20, 22, 21, 25, 31, 31, …
## $ hp_pct <dbl> 0.20494700, 0.20494700, 0.14487633, 0.20494700, 0.43462898, 0…
reactable_dl(data)
colDef()colDef(name=...) to rename headerscolFormat() for numeric formatting (digits,
percent, currency)reactable_dl(
data,
columns = list(
model = colDef(name = "Model", sticky = "left", width = 160),
mpg = colDef(name = "Miles/Gallon", format = colFormat(digits = 1)),
cyl = colDef(name = "Cylinders", align = "center", width = 100),
disp = colDef(name = "Displacement (cu in)", format = colFormat(digits = 0)),
hp = colDef(name = "Horsepower", align = "center"),
hp_pct = colDef(name = "HP (0–1)", format = colFormat(percent = TRUE, digits = 0)),
wt = colDef(name = "Weight (1000 lbs)", format = colFormat(digits = 2)),
am = colDef(name = "Transmission")
)
)
reactable_dl(
data,
searchable = TRUE,
pagination = TRUE,
defaultPageSize = 10,
highlight = TRUE,
filterable = TRUE
)
searchable = TRUE adds a global search boxfilterable = TRUE adds per‑column filtersTwo quick styles: continuous color scale and
in‑cell data bars. These helpers come from
{reactablefmtr}.
reactable_dl(
data,
columns = list(
mpg = colDef(
name = "Miles/Gallon",
cell = function(value) reactablefmtr::color_tiles(value, colors = c("#fde0dd", "#fa9fb5", "#c51b8a")),
align = "center"
),
hp = colDef(
name = "Horsepower",
cell = function(value) reactablefmtr::data_bars(value,
text_position = "inside-end",
number_fmt = scales::label_number(accuracy = 0.1)),
align = "center"
),
wt = colDef(
name = "Weight",
cell = function(value) reactablefmtr::color_scales(value, colors = c("#f0f9e8", "#7bccc4", "#084081")),
align = "center"
),
model = colDef(name = "Model", sticky = "left", width = 160)
),
defaultPageSize = 12,
bordered = TRUE,
striped = TRUE
)
You can group rows by a factor and add column groups to organize headers.
reactable_dl(
data %>% arrange(cyl, desc(mpg)),
groupBy = "cyl",
defaultSorted = list(mpg = "desc"),
columns = list(
model = colDef(name = "Model", sticky = "left"),
cyl = colDef(name = "Cyl"),
mpg = colDef(name = "MPG", format = colFormat(digits = 1)),
hp = colDef(name = "HP"),
disp = colDef(name = "Disp"),
wt = colDef(name = "Weight", format = colFormat(digits = 2)),
am = colDef(name = "Trans")
),
columnGroups = list(
colGroup(name = "Performance", columns = c("mpg", "hp", "disp")),
colGroup(name = "Specs", columns = c("wt", "am"))
),
striped = TRUE,
bordered = TRUE
)
Great for metadata, notes, or longer text fields.
reactable_dl(
data,
details = function(index) {
row <- data[index, ]
htmltools::div(style = "padding: 8px;",
htmltools::tags$strong(row$model), htmltools::br(),
sprintf("MPG Rank: %s", row$mpg_rank), htmltools::br(),
sprintf("Transmission: %s", row$am)
)
},
defaultExpanded = FALSE,
highlight = TRUE
)
reactableTheme()Polish the look & feel to match a report or dashboard aesthetic.
reactable_dl(
data,
theme = reactableTheme(
color = "#222",
backgroundColor = "#fff",
borderColor = "#e5e7eb",
stripedColor = "#f9fafb",
highlightColor = "#f1f5f9",
searchInputStyle = list(borderColor = "#e5e7eb", background = "#fff"),
rowSelectedStyle = list(backgroundColor = "#eef2ff"),
headerStyle = list(background = "#f3f4f6", borderColor = "#e5e7eb",
fontWeight = 700, letterSpacing = "0.3px")
),
striped = TRUE,
bordered = TRUE,
defaultPageSize = 12,
highlight = TRUE,
filterable = TRUE
)
Create a small wrapper to standardize your table style across projects.
make_table <- function(df, page_size = 12) {
reactable_dl(
df,
searchable = TRUE,
filterable = TRUE,
defaultPageSize = page_size,
pagination = TRUE,
highlight = TRUE,
bordered = TRUE,
striped = TRUE,
downloadButton = TRUE,
theme = reactableTheme(
color = "#222", backgroundColor = "#fff", borderColor = "#e5e7eb",
stripedColor = "#f9fafb", highlightColor = "#f1f5f9",
headerStyle = list(background = "#f3f4f6", borderColor = "#e5e7eb",
fontWeight = 700)
)
)
}
make_table(data)
my_data.csvmake_table()# my <- readr::read_csv("my_data.csv") %>% janitor::clean_names()
# make_table(my)
flexdashboard or
quarto for instant interactivityWhat I learned: reactable adds a lot of
value with very little code. For many reports, it’s the fastest path to
interactivity without building a Shiny app.
{reactable} CRAN: https://cran.r-project.org/package=reactable{reactablefmtr} docs: https://kcuilla.github.io/reactablefmtr/