Great Tables can render a fully interactive table (with sorting, searching, filtering, and pagination) by calling opt_interactive(). The result is a self-contained HTML widget powered by DataTables that works in Jupyter notebooks, Quarto documents, and any HTML output.
Interactive tables are ideal for exploratory contexts where the reader needs to find specific values in a larger dataset (e.g., dashboards, internal tools, or notebook-based analysis). Sorting and filtering let readers answer their own questions about the data without you having to anticipate every view. For static outputs like PDF reports, printed documents, or email, stick with static tables since the interactivity features won’t be functional in those formats.
Enabling interactivity
A single call to opt_interactive() switches the table from static to interactive. All of the normal formatting and styling methods continue to work exactly as before.
from great_tables import GT
from great_tables.data import gtcars
import polars as pl
gtcars_pl = pl.from_pandas(gtcars).select(["mfr", "model", "year", "hp", "trq", "msrp"])
(
GT(gtcars_pl)
.tab_header(title="GT Cars", subtitle="The full dataset, interactively")
.cols_label(mfr="Make", model="Model", year="Year", hp="HP", trq="Torque", msrp="MSRP")
.fmt_integer(columns=["year", "hp", "trq"], use_seps=False)
.fmt_currency(columns="msrp")
.opt_interactive()
)
Click on any column header to sort. The table is paginated by default with 10 rows per page.
One important thing to note: all of your existing formatting, styling, and structural methods (headers, footnotes, spanners) continue to work in interactive mode. You don’t need to rebuild your table or learn a separate API. Instead, just append .opt_interactive() to the end of your method chain and the table gains sorting and pagination while keeping everything else intact.
Search and per-column filters
Pass use_search=True to add a global search box, and use_filters=True to add a per-column text filter row beneath the headers.
from great_tables import GT
from great_tables.data import gtcars
import polars as pl
(
GT(gtcars_pl)
.tab_header(title="GT Cars: Search and Filter")
.cols_label(mfr="Make", model="Model", year="Year", hp="HP", trq="Torque", msrp="MSRP")
.fmt_integer(columns=["year", "hp", "trq"], use_seps=False)
.fmt_currency(columns="msrp")
.opt_interactive(use_search=True, use_filters=True)
)
Try typing "Audi" in the Make filter or "2017" in the Year filter to narrow the results.
The tab_style() methods works in interactive mode for loc.body() and loc.column_labels(). Styles are applied row-by-row as DataTables renders each page, so they follow the data when you sort or filter.
from great_tables import style, loc
(
GT(gtcars_pl)
.tab_header(title="GT Cars: Targeted styles")
.cols_label(mfr="Make", model="Model", year="Year", hp="HP", trq="Torque", msrp="MSRP")
.fmt_integer(columns=["year", "hp", "trq"], use_seps=False)
.fmt_currency(columns="msrp")
.tab_style(
style=style.fill(color="#fff3cd"),
locations=loc.body(columns="hp", rows=pl.col("hp") > 500),
)
.tab_style(
style=[style.text(color="#c0392b", weight="bold")],
locations=loc.body(columns="msrp", rows=pl.col("msrp") > 200_000),
)
.tab_style(
style=[style.fill(color="#ddeeff"), style.text(weight="bold")],
locations=loc.column_labels(columns=["hp", "trq"]),
)
.opt_interactive(use_search=True, use_filters=True, page_size_default=10)
)
From the output you might notice:
- rows where HP exceeds 500 are highlighted in amber
- MSRP values over $200,000 appear in bold red
- the
HP and Torque column headers have a blue-tinted background
Sort by HP or MSRP to confirm that the styling moves with the data.
The use of data_color() is fully supported in interactive tables. It applies a continuous color scale to each cell based on the underlying value. Because data_color() is implemented via tab_style() internally, it inherits the same interactive behavior automatically.
(
GT(gtcars_pl)
.tab_header(title="GT Cars: data_color()")
.cols_label(mfr="Make", model="Model", year="Year", hp="HP", trq="Torque", msrp="MSRP")
.fmt_integer(columns=["year", "hp", "trq"], use_seps=False)
.fmt_currency(columns="msrp")
.data_color(columns="hp", palette=["#fff7ec", "#ef6c00"])
.data_color(columns="msrp", palette="Blues")
.opt_interactive(use_search=True, use_filters=True, page_size_default=10)
)
Each column uses its own independent scale: orange for HP and blue for MSRP. Try filtering by Make to see how the scales update in context.
Multiple columns, shared scale
Pass domain= to lock the color range across columns so they share a common scale.
(
GT(gtcars_pl)
.tab_header(
title="GT Cars: HP and Torque on a shared scale",
subtitle="Higher values are darker and both columns use the same domain",
)
.cols_label(mfr="Make", model="Model", year="Year", hp="HP", trq="Torque", msrp="MSRP")
.fmt_integer(columns=["year", "hp", "trq"], use_seps=False)
.fmt_currency(columns="msrp")
.data_color(
columns=["hp", "trq"],
palette=["#edf8fb", "#006d2c"],
domain=[200, 700],
)
.opt_interactive(use_search=True, page_size_default=10)
)