Interactive Tables with reactable

· 4 min read · Updated March 12, 2026 · beginner
r reactable data-viz tables

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 name
  • width: Column width in pixels
  • align: Alignment (“left”, “center”, “right”)
  • format: Number/date formatting
  • sortable: Enable/disable sorting
  • filterable: Enable/disable filtering
  • resizable: Allow column resize
  • cell: 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 page
  • showPageSizeOptions: Display page size selector
  • pageSizeOptions: Available page sizes
  • defaultPage: 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