Skip to contents

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

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>

Arguments

  • 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:

    Property Example Description
    values { Petal.Length: 1.7, Species: "setosa" } row data values
    index 20 row index (zero-based)
  • 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.

Details

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.

Table Search Methods

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>

Arguments

  • 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:

    Property Example Description
    values { Petal.Length: 1.7, Species: "setosa" } row data values
    index 20 row index (zero-based)
  • 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.

Details

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

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.

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 function

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 function

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 properties

Property Example Description
id "Petal.Length" column ID
name "Petal Length" column display name
filterValue "petal" column filter value
setFilter function setFilter(value: any) function to set the column filter value (set to undefined to clear the filter)

state properties

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

Examples

Basic custom filter method

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
)

Basic custom search method

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
)

Exact text match

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
)

Numeric value filtering

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
)

Regular expression pattern filtering

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
)

Fuzzy text searching

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
  )
))

Select input filter

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"
)

Data list filter

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"
)

Range filter

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"
)

External range filter

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"
  )
))
7

External checkbox filter

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"
  )
))

Basic custom filter input (React)

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
)

Range filter (React)

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
)