Interactive Tables with reactable
The reactable package builds interactive data tables for R using React. These tables run in R Markdown documents, Shiny applications, and standalone HTML pages. Reactable tables support sorting, filtering, pagination, row selection, and custom cell rendering without requiring JavaScript knowledge.
installation
Install reactable from CRAN:
install.packages("reactable")
You also need the htmlwidgets package for rendering:
install.packages("htmlwidgets")
For advanced formatting features, install the reactablefmtr package:
install.packages("reactablefmtr")
basic-usage
Create a basic interactive table with the reactable() function:
library(reactable)
# Sample data
sample_data <- data.frame(
name = c("Alice", "Bob", "Charlie", "Diana", "Eve"),
age = c(28, 35, 42, 31, 29),
score = c(85, 92, 78, 88, 95),
department = c("Engineering", "Marketing", "Sales", "Engineering", "HR")
)
# Create basic table
reactable(sample_data)
This renders a table with sortable columns, a search box, and pagination controls. Click any column header to sort. Use the search box to filter rows.
column-definitions-and-formatting
Control column behavior with colDef() within columns argument:
reactable(
sample_data,
columns = list(
name = colDef(name = "Employee Name"),
age = colDef(
name = "Age",
format = colFormat(suffix = " years")
),
score = colDef(
name = "Performance Score",
format = colFormat(digits = 1),
style = function(value) {
color <- if (value >= 90) "#2ecc71" else if (value >= 80) "#f39c12" else "#e74c3c"
list(color = color)
}
),
department = colDef(
name = "Department",
filterable = TRUE,
cell = function(value) {
paste0("📁 ", value)
}
)
)
)
Column definitions accept:
name: Display namewidth: Column width in pixelsalign: Alignment (“left”, “center”, “right”)format: Number/date formattingsortable: Enable/disable sortingfilterable: Enable/disable filteringresizable: Allow column resizecell: Custom cell renderer
sorting-and-filtering
Enable sorting on specific columns:
reactable(
sample_data,
sortable = TRUE,
defaultSorted = "score",
defaultSortOrder = "desc",
columns = list(
name = colDef(sortable = TRUE),
age = colDef(sortable = TRUE),
score = colDef(sortable = TRUE),
department = colDef(sortable = TRUE)
)
)
Add column-specific filters:
reactable(
sample_data,
filterable = TRUE,
columns = list(
department = colDef(
filterMethod = JS("function(rows, columnId, filterValue) {
return rows.filter(function(row) {
return row.values[columnId].toLowerCase().includes(filterValue.toLowerCase())
})
}")
)
)
)
The default filter performs case-insensitive substring matching. Use custom JavaScript functions for advanced filtering logic.
pagination
Control pagination with these options:
reactable(
sample_data,
pagination = TRUE,
pageSize = 10,
showPageSizeOptions = TRUE,
pageSizeOptions = c(5, 10, 25, 50),
defaultPage = 1
)
pagination: Enable pagination (default: TRUE for >10 rows)pageSize: Default rows per pageshowPageSizeOptions: Display page size selectorpageSizeOptions: Available page sizesdefaultPage: Initial page number
For client-side pagination, all data loads at once. For server-side pagination in Shiny, use remote() option and handle page events in your server function.
custom-cell-rendering
Render custom cell content with the cell parameter:
# Custom cell with progress bar
reactable(
sample_data,
columns = list(
score = colDef(
cell = function(value) {
# Create progress bar
width <- paste0(value, "%")
bar <- htmltools::tags$div(
style = list(width = width, background = "#3498db", height = "100%"),
class = "progress-bar"
)
container <- htmltools::tags$div(
style = list(width = "100%", background = "#ecf0f1", height = "20px"),
bar
)
htmltools::tagList(container, paste(value, "points"))
}
)
)
)
Use htmltools to build complex HTML elements within cells. This approach works for embedding:
- Images and icons
- Links and buttons
- Progress bars and meters
- Nested tables
- Any HTML content
conditional-styling
Apply conditional formatting based on cell values:
reactable(
sample_data,
columns = list(
score = colDef(
style = function(value) {
# Color based on score ranges
color <- case_when(
value >= 90 ~ "#27ae60",
value >= 80 ~ "#2980b9",
value >= 70 ~ "#f39c12",
TRUE ~ "#c0392b"
)
list(color = color, fontWeight = "bold")
}
),
age = colDef(
style = function(value) {
# Highlight ages above 35
if (value > 35) {
list(background = "#ffe6e6")
} else {
list(background = "white")
}
}
)
)
)
Apply row-level styling with the rowStyle option:
reactable(
sample_data,
rowStyle = function(index) {
if (sample_data$score[index] < 80) {
list(background = "#fff5f5")
}
}
)
interactive-features
Reactable tables support user interaction beyond basic sorting and filtering:
Row Selection
reactable(
sample_data,
selection = "multiple",
onClick = "select",
rowClass = function(index) {
if (index %% 2 == 0) "even-row"
}
)
Expandable Rows
reactable(
sample_data,
details = function(index) {
details_data <- sample_data[index, ]
htmltools::tags$div(
htmltools::tags$h4("Additional Details"),
htmltools::tags$p(paste("Employee ID:", 1000 + index)),
htmltools::tags$p(paste("Start Date:", Sys.Date() - sample_data$age[index] * 365))
)
}
)
Embed in Shiny
Use reactable in Shiny applications:
library(shiny)
ui <- fluidPage(
titlePanel("Employee Dashboard"),
reactableOutput("table")
)
server <- function(input, output) {
output$table <- renderReactable({
reactable(
sample_data,
columns = list(
name = colDef(cell = function(value) {
htmltools::tags$a(href = paste0("/employee/", value), value)
})
),
searchable = TRUE
)
})
}
shinyApp(ui, server)
see-also
- Filter Data with dplyr — Learn to filter data frames before rendering tables
- Select Columns with dplyr — Choose specific columns for display
- Sort Data with dplyr — Order data before table rendering