knitr::opts_chunk$set(echo = TRUE) library(reactable) library(htmltools) propsTable <- function(props) { tags$div( style = "overflow: auto;", tabindex = "0", tags$table( class = "props-tbl", tags$thead( tags$tr( lapply(colnames(props), function(name) tags$th(name)) ) ), tags$tbody( apply(props, 1, function(row) { tags$tr( tags$th(scope = "row", tags$code(row[["Property"]])), tags$td(tags$code(row[["Example"]])), tags$td(row[["Description"]]) ) }) ) ) ) }
Custom filter methods and custom filter inputs let you change how filtering is done in reactable. By default, all filter inputs are text inputs that filter data using a case-insensitive text match, or for numeric columns, a prefix match.
Column filter methods, table search methods, and column filter inputs can all be customized separately for flexibility. In some cases, you may want to keep the default filter input but change the filter method, or in other cases, vice versa.
Column filter methods can be customized using the filterMethod
argument in colDef()
:
colDef( filterMethod = JS(" function(rows, columnId, filterValue) { /* ... */ return filteredRows } ") )
filterMethod
should be a JavaScript function, wrapped in JS()
, that takes
3 arguments — the rows, column ID, and filter value — and returns the filtered
array of rows.
filterMethod( rows: Array<Row>, columnId: string, filterValue: any ): Array<Row>
rows
, an array of row objects. rows
consists of data rows only, and does
not include aggregated rows when the table is grouped. Each Row
has the following
properties:
```r rowProps <- dplyr::tribble( ~Property, ~Example, ~Description, "values", '{ Petal.Length: 1.7, Species: "setosa" }', "row data values", "index", "20", "row index (zero-based)" )
propsTable(rowProps) ```
columnId
, the column ID. Use this to access the cell value, like
row.values[columnId]
.
filterValue
, the column filter value. This will be a string when using the
default filter inputs.filterMethod
is not called when the filter value is cleared, so you don't need to
specifically handle the case where filterValue
is unset (undefined
).
filterMethod
only applies to individual column filters, and does not affect global
table searching.
The global table search method can be customized using the searchMethod
argument
in reactable()
:
reactable( searchMethod = JS(" function(rows, columnIds, filterValue) { /* ... */ return filteredRows } ") )
searchMethod
should be a JavaScript function, wrapped in JS()
, that takes
3 arguments — the rows, column IDs, and search value — and returns the filtered
array of rows.
searchMethod( rows: Array<Row>, columnIds: Array<string>, searchValue: any ): Array<Row>
rows
, an array of row objects. rows
consists of data rows only, and does
not include aggregated rows when the table is grouped. Each Row
has the following
properties:
```r rowProps <- dplyr::tribble( ~Property, ~Example, ~Description, "values", '{ Petal.Length: 1.7, Species: "setosa" }', "row data values", "index", "20", "row index (zero-based)" )
propsTable(rowProps) ```
columnIds
, an array of column IDs. Use this to access the cell values
for the columns being searched, like row.values[columnId]
.
searchValue
, the search value. This will be a string when using the
default search input.searchMethod
is not called when the search value is cleared, so you don't need to
specifically handle the case where searchValue
is unset (undefined
).
Column filter inputs are customized using the filterInput
argument in colDef()
.
filterInput
can either be an element or a function that returns an element to be
rendered in place of the default column filter.
Unlike custom rendering of other table elements, custom filter inputs must communicate back to the table on filtering changes, which can be tricky.
In many simpler cases, you can write your custom filter input in R and use
Reactable.setFilter()
from the
reactable JavaScript API to notify the table of filter changes. Note that the table
must have a unique elementId
to use Reactable.setFilter()
— see
Using the JavaScript API for more details.
For more advanced customization, you can write your filter input entirely in
JavaScript, using the React JavaScript library to create an element that gets and sets
the filter value. In reactable, JavaScript render functions can return React elements,
and reactable comes with React as a dependency via the reactR package.
Use reactR::html_dependency_react()
to explicitly include this dependency or find the
version of React in use.
For more information on React or using React from R, see the React documentation and reactR package documentation.
::: {.callout-tip}
Tip: Many column filter inputs do not have a visible text label, including
the default text inputs. If your custom filter does not have a visible text label,
be sure to give it an accessible name using the
aria-label
attribute or similar technique.
:::
R render functions take up to 2 optional arguments — the column values and column name — and return the element to render.
colDef( filterInput = function(values, name) { # input: # - values, the column values (optional) # - name, the column name (optional) # # output: # - element to render (e.g. an HTML tag or HTML string) htmltools::tags$input( type = "text", onchange = sprintf("Reactable.setFilter('tbl', '%s', this.value)", name), "aria-label" = sprintf("Filter %s", name), style = "width: 100%;" ) } )
JavaScript render functions, wrapped in JS()
, take up to 2 optional arguments — a column object
and a table state object — and return the element to render.
colDef( filterInput = JS(" function(column, state) { // input: // - column, an object containing column properties // - state, an object containing the table state // // output: // - element to render (e.g. an HTML string or React element) return React.createElement('input', { type: 'text', value: column.filterValue, onChange: function(e) { return column.setFilter(e.target.value || undefined) }, 'aria-label': 'Filter ' + column.name }) } ") )
column
propertiescolumnProps <- dplyr::tribble( ~Property, ~Example, ~Description, "id", '"Petal.Length"', "column ID", "name", '"Petal Length"', "column display name", "filterValue", '"petal"', "column filter value", "setFilter", 'function setFilter(value: any)', tagList("function to set the column filter value", "(set to ", tags$code("undefined"), " to clear the filter)") ) propsTable(columnProps)
state
propertiesstateProps <- dplyr::tribble( ~Property, ~Example, ~Description, "sorted", '[{ id: "Petal.Length", desc: true }, ...]', "columns being sorted in the table", "page", "2", "page index (zero-based)", "pageSize", "10", "page size", "pages", "5", "number of pages", "filters", '[{ id: "Species", value: "petal" }]', "column filter values", "searchValue", '"petal"', "table search value", "selected", '[0, 1, 4]', "selected row indices (zero-based)", "pageRows", '[{ Petal.Length: 1.7, Species: "setosa" }, ...]', "current row data on the page", "sortedData", '[{ Petal.Length: 1.7, Species: "setosa" }, ...]', "current row data in the table (after sorting, filtering, grouping)", "data", '[{ Petal.Length: 1.7, Species: "setosa" }, ...]', "original row data in the table", "hiddenColumns", '["Petal.Length"]', "columns being hidden in the table" ) propsTable(stateProps)
This example shows basic usage of a custom filter method, changing filtering
on the Manufacturer
column to be case-sensitive rather than case-insensitive.
(Try filtering for "bmw" and then "BMW").
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] reactable( data, columns = list( Manufacturer = colDef( filterable = TRUE, # Filter by case-sensitive text match filterMethod = JS("function(rows, columnId, filterValue) { return rows.filter(function(row) { return row.values[columnId].indexOf(filterValue) !== -1 }) }") ) ), defaultPageSize = 5 )
This example shows basic usage of a custom search method, changing global searching to be a case-sensitive text match on all columns. (Try searching for "bmw" and then "BMW").
Note that some columns may be numeric or another non-string type, so you can use
String()
to convert values to strings before calling string methods like
indexOf()
.
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] reactable( data, searchable = TRUE, # Search by case-sensitive text match searchMethod = JS("function(rows, columnIds, searchValue) { return rows.filter(function(row) { return columnIds.some(function(columnId) { return String(row.values[columnId]).indexOf(searchValue) !== -1 }) }) }"), defaultPageSize = 5 )
This example shows how you can filter a column using a case-sensitive exact text match. (Try searching for "BMW" and then "bmw").
Note that some columns may be numeric or another non-string type, so you can use
String()
to convert values to strings before comparing them with the filter value.
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] reactable( data, columns = list( Manufacturer = colDef( filterable = TRUE, # Filter by case-sensitive exact text match filterMethod = JS("function(rows, columnId, filterValue) { return rows.filter(function(row) { return String(row.values[columnId]) === filterValue }) }") ) ), defaultPageSize = 5 )
This example shows how you can filter a numeric column based on a minimum value.
(Try filtering the Price
column for 30
).
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] reactable( data, columns = list( Price = colDef( filterable = TRUE, # Filter by minimum price filterMethod = JS("function(rows, columnId, filterValue) { return rows.filter(function(row) { return row.values[columnId] >= filterValue }) }") ) ), defaultPageSize = 5 )
This example shows how you can filter a column based on a regular expression pattern. Note that the regular expression is not escaped here — see regular expression escaping for an example of how to escape special characters in regular expressions.
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] reactable( data, columns = list( Manufacturer = colDef( filterable = TRUE, # Filter by case-insensitive text match filterMethod = JS("function(rows, columnId, filterValue) { const pattern = new RegExp(filterValue, 'i') return rows.filter(function(row) { return pattern.test(row.values[columnId]) }) }") ) ), defaultPageSize = 5 )
This example uses the match-sorter JavaScript library to add fuzzy searching to a table. (Try searching "adi" to match both "Cadillac" and "Audi").
This also shows how you can create reusable search or filter methods that can be shared across multiple tables or columns.
library(htmltools) data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] # match-sorter library dependency. Include this anywhere in your document or app. matchSorterDep <- htmlDependency( "match-sorter", "1.8.0", c(href = "https://unpkg.com/match-sorter@1.8.0/dist/umd/"), script = "match-sorter.min.js" ) # Fuzzy search method based on match-sorter # See https://github.com/kentcdodds/match-sorter for advanced customization matchSorterSearchMethod <- JS("function(rows, columnIds, searchValue) { const keys = columnIds.map(function(id) { return function(row) { return row.values[id] } }) return matchSorter(rows, searchValue, { keys: keys }) }") browsable(tagList( matchSorterDep, reactable( data, searchable = TRUE, searchMethod = matchSorterSearchMethod, defaultPageSize = 5 ) ))
This example shows how you can render a custom
<select>
input filter in R.
The <select>
input filters the Manufacturer
column from a set of unique values,
and includes an additional "All" option to clear the filter.
library(htmltools) data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] reactable( data, filterable = TRUE, columns = list( Manufacturer = colDef( filterInput = function(values, name) { tags$select( # Set to undefined to clear the filter onchange = sprintf("Reactable.setFilter('cars-select', '%s', event.target.value || undefined)", name), # "All" has an empty value to clear the filter, and is the default option tags$option(value = "", "All"), lapply(unique(values), tags$option), "aria-label" = sprintf("Filter %s", name), style = "width: 100%; height: 28px;" ) } ) ), defaultPageSize = 5, elementId = "cars-select" )
The <datalist>
element
is like a native text input, but with an autocomplete feature that lets you choose from a
set of unique options. (Try searching for "ac" in the Manufacturer
column.)
This example also shows how you can create reusable filter inputs, or set default custom filters based on column type.
library(htmltools) data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] # Creates a data list column filter for a table with the given ID dataListFilter <- function(tableId, style = "width: 100%; height: 28px;") { function(values, name) { dataListId <- sprintf("%s-%s-list", tableId, name) tagList( tags$input( type = "text", list = dataListId, oninput = sprintf("Reactable.setFilter('%s', '%s', event.target.value || undefined)", tableId, name), "aria-label" = sprintf("Filter %s", name), style = style ), tags$datalist( id = dataListId, lapply(unique(values), function(value) tags$option(value = value)) ) ) } } reactable( data, filterable = TRUE, columns = list( # Use data list filter for a specific column Manufacturer = colDef( filterInput = dataListFilter("cars-list") ) ), # Or use data list filter as the default for all factor columns defaultColDef = colDef( filterInput = function(values, name) { if (is.factor(values)) { dataListFilter("cars-list")(values, name) } } ), defaultPageSize = 5, elementId = "cars-list" )
This is a basic example of a native
<input type="range">
element
for numeric filtering. The Price
column is filtered by minimum value.
library(htmltools) data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] reactable( data, columns = list( Price = colDef( filterable = TRUE, filterMethod = JS("function(rows, columnId, filterValue) { return rows.filter(function(row) { return row.values[columnId] >= filterValue }) }"), filterInput = function(values, name) { oninput <- sprintf("Reactable.setFilter('cars-range', '%s', this.value)", name) tags$input( type = "range", min = floor(min(values)), max = ceiling(max(values)), value = floor(min(values)), oninput = oninput, onchange = oninput, # For IE11 support "aria-label" = sprintf("Filter by minimum %s", name) ) } ) ), defaultPageSize = 5, elementId = "cars-range" )
This example shows how you can create custom filter inputs outside the table, using a more complex version of the range filter from above.
library(htmltools) # Custom range input filter with label and value rangeFilter <- function(tableId, columnId, label, min, max, value = NULL, step = NULL, width = "200px") { value <- if (!is.null(value)) value else min inputId <- sprintf("filter_%s_%s", tableId, columnId) valueId <- sprintf("filter_%s_%s__value", tableId, columnId) oninput <- paste( sprintf("document.getElementById('%s').textContent = this.value;", valueId), sprintf("Reactable.setFilter('%s', '%s', this.value)", tableId, columnId) ) div( tags$label(`for` = inputId, label), div( style = sprintf("display: flex; align-items: center; width: %s", validateCssUnit(width)), tags$input( id = inputId, type = "range", min = min, max = max, step = step, value = value, oninput = oninput, onchange = oninput, # For IE11 support style = "width: 100%;" ), span(id = valueId, style = "margin-left: 8px;", value) ) ) } # Filter method that filters numeric columns by minimum value filterMinValue <- JS("function(rows, columnId, filterValue) { return rows.filter(function(row) { return row.values[columnId] >= filterValue }) }")
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] browsable(tagList( rangeFilter( "cars-ext-range", "Price", "Filter by Minimum Price", floor(min(data$Price)), ceiling(max(data$Price)) ), reactable( data, columns = list( Price = colDef(filterMethod = filterMinValue) ), defaultPageSize = 5, elementId = "cars-ext-range" ) ))
This example shows how you can filter a column with logical values using an external checkbox input.
The data contains a lot of missing values, and we want a checkbox that allows you
to show just the rows with missing values. So we add a hidden column that indicates
with TRUE
or FALSE
whether any values in the row are missing, and filter for
true
values when the checkbox is checked.
library(htmltools) data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] # Add missing values to the data set.seed(123) data[] <- lapply(data, function(x) { x[sample(1:length(x), length(x) / 3)] <- NA x }) # Indicates with TRUE or FALSE whether any values in the row are missing data$has_missing <- !complete.cases(data) browsable(tagList( tags$label( tags$input( type = "checkbox", onclick = "Reactable.setFilter('cars-missing', 'has_missing', event.target.checked)" ), "Show missing values only" ), reactable( data, columns = list( # Hidden column for filtering missing values has_missing = colDef( show = FALSE, filterMethod = JS("function(rows, columnId, filterValue) { if (filterValue === true) { return rows.filter(function(row) { const hasMissing = row.values[columnId] return hasMissing }) } return rows }") ) ), defaultColDef = colDef(na = "-"), defaultPageSize = 5, elementId = "cars-missing" ) ))
This example shows how you could write your custom filter input entirely in
JavaScript, using React. It renders a basic text input filter for the
Manufacturer
column.
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] reactable( data, columns = list( Manufacturer = colDef( filterable = TRUE, filterInput = JS("function(column) { return React.createElement('input', { type: 'text', value: column.filterValue, onChange: function(event) { // Set to undefined to clear the filter return column.setFilter(event.target.value || undefined) }, 'aria-label': 'Filter ' + column.name, style: { width: '100%' } }) }") ) ), defaultPageSize = 5 )
Here's a more complex example of a React-based filter input, with filter values
that depend on the column data. The min and max values of the column are found
dynamically from state.data
.
The JavaScript code is embedded as a separate
js
language chunk
to make it easier to work with. but it could also be included through an external
JavaScript file, or inlined in R using htmltools::tags$script()
.
// Custom range filter with value label function rangeFilter(column, state) { // Get min and max values from raw table data let min = Infinity let max = 0 state.data.forEach(function(row) { const value = row[column.id] if (value < min) { min = Math.floor(value) } else if (value > max) { max = Math.ceil(value) } }) const filterValue = column.filterValue || min const input = React.createElement('input', { type: 'range', value: filterValue, min: min, max: max, onChange: function(event) { // Set to undefined to clear the filter column.setFilter(event.target.value || undefined) }, style: { width: '100%', marginRight: '8px' }, 'aria-label': 'Filter ' + column.name }) return React.createElement( 'div', { style: { display: 'flex', alignItems: 'center', height: '100%' } }, [input, filterValue] ) } // Filter method that filters numeric columns by minimum value function filterMinValue(rows, columnId, filterValue) { return rows.filter(function(row) { return row.values[columnId] >= filterValue }) }
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")] reactable( data, filterable = TRUE, columns = list( Price = colDef( filterMethod = JS("filterMinValue"), filterInput = JS("rangeFilter") ) ), defaultPageSize = 5, minRows = 5 )
```{css echo=FALSE} / rmarkdown html documents / .main-container { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; }
.main-container blockquote { font-size: inherit; } ```
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.