
reactable Integration
Saiem Gilani
Source: vignettes/reactable-integration.Rmd
reactable-integration.RmdOn this page
Overview
sdvplotR provides a family of helpers for embedding team
logos, wordmarks, player headshots, and team-colored bars in reactable
tables. These complement the existing gt helpers
(gt_sdv_logos() et al.) and are designed for interactive /
web-first dashboards.
All functions follow the same league / variant /
size contract as their ggplot2 and gt
counterparts:
| Argument | Purpose |
|---|---|
league |
One of sdv_leagues()
|
variant |
"default", "dark" or a named variant of
the league’s marks (sdv_marks() lists them) |
height |
Image height in pixels |
team_col, value_col
|
Column names for color / bar resolution |
The image helpers (reactable_sdv_logos(),
reactable_sdv_headshots(), …) return HTML, so their columns
need html = TRUE.
Basic: Team logos in a standings table
library(reactable)
library(sdvplotR)
library(dplyr)
# Regular-season records from nflreadr's schedules, one row per team per game,
# for the last completed regular season (NFL seasons are named for the year they
# start and end in early January)
season <- as.integer(format(Sys.Date(), "%Y")) - 1 -
(format(Sys.Date(), "%m-%d") < "01-15")
standings <- nflreadr::load_schedules(season) |>
filter(game_type == "REG", !is.na(result)) |>
nflreadr::clean_homeaway() |>
group_by(team_abbr = team) |>
summarise(
wins = sum(team_score > opponent_score),
losses = sum(team_score < opponent_score),
pct = mean(team_score > opponent_score) + 0.5 * mean(team_score == opponent_score),
.groups = "drop"
) |>
arrange(desc(pct)) |>
slice_head(n = 16)
reactable(
standings,
columns = list(
team_abbr = colDef(
name = "Team",
cell = reactable_sdv_logos(league = "nfl", height = 28),
html = TRUE
),
pct = colDef(
format = colFormat(digits = 3),
style = reactable_sdv_team_color_bar(
standings,
team_col = "team_abbr",
league = "nfl",
max_value = 1
)
)
)
)Dark mode variants
reactable tables often sit inside dark-themed
dashboards. Use variant = "dark" for high-contrast logos on
dark backgrounds:
reactable(
standings,
theme = reactableTheme(
color = "#e0e0e0",
backgroundColor = "#1a1a1a",
borderColor = "#333"
),
columns = list(
team_abbr = colDef(
cell = reactable_sdv_logos(league = "nfl", variant = "dark", height = 28),
html = TRUE
),
pct = colDef(format = colFormat(digits = 3))
)
)Logo column headers
Replace column headers (e.g., per-team stat columns) with the team’s logo:
# A wide table with one column per team (example values)
wide_df <- data.frame(
week = 1:4,
KC = c(0.21, 0.05, 0.14, 0.30), BUF = c(0.12, 0.25, 0.08, 0.18),
SF = c(0.09, 0.16, 0.22, 0.04), DAL = c(-0.03, 0.11, 0.02, 0.07),
PHI = c(0.15, 0.19, 0.12, 0.23), MIA = c(0.01, -0.06, 0.10, 0.03)
)
reactable(
wide_df,
columns = reactable_sdv_cols_label(
wide_df,
league = "nfl",
variant = "default",
height = 26
)
)Team-colored rows
Use reactable_sdv_team_color_bg() to tint entire rows
with the team’s primary color at low opacity — useful for scanning
standings quickly:
reactable(
standings,
columns = list(
team_abbr = colDef(
cell = reactable_sdv_logos(league = "nfl", height = 28),
html = TRUE
),
wins = colDef(style = reactable_sdv_team_color_bg(standings, "team_abbr", league = "nfl")),
losses = colDef(style = reactable_sdv_team_color_bg(standings, "team_abbr", league = "nfl")),
pct = colDef(
format = colFormat(digits = 3),
style = reactable_sdv_team_color_bg(standings, "team_abbr", league = "nfl")
)
)
)Player headshots
Headshots use the same reactable_sdv_headshots() helper
across all leagues. NFL headshots take GSIS IDs, the
gsis_id in nflreadr’s rosters:
roster <- nflreadr::load_rosters(seasons = season) |>
filter(team %in% c("KC", "BUF", "SF"), position == "QB", !is.na(gsis_id)) |>
select(full_name, team, position, gsis_id)
reactable(
roster,
columns = list(
full_name = colDef(name = "Player"),
team = colDef(cell = reactable_sdv_logos(league = "nfl", height = 24), html = TRUE),
gsis_id = colDef(
name = "Headshot",
cell = reactable_sdv_headshots(league = "nfl", id_system = "gsis", height = 40),
html = TRUE
)
)
)Cross-league examples
All helpers work identically across leagues — only the
league argument changes.
NBA standings (hoopR)
# the last completed regular season (named for the year it ends, in mid-April)
nba_season <- as.integer(format(Sys.Date(), "%Y")) -
(format(Sys.Date(), "%m-%d") < "04-20")
nba_standings <- hoopR::load_nba_standings(seasons = nba_season) |>
filter(stat_name %in% c("wins", "losses", "winPercent")) |>
select(team = team_abbreviation, conference = group_abbreviation, stat_name, value) |>
tidyr::pivot_wider(names_from = stat_name, values_from = value) |>
arrange(desc(winPercent)) |>
slice_head(n = 10)
reactable(
nba_standings,
columns = list(
team = colDef(cell = reactable_sdv_logos(league = "nba", height = 28), html = TRUE),
winPercent = colDef(name = "Pct", format = colFormat(digits = 3))
)
)CFB power ratings (cfbfastR)
ESPN’s Football Power Index, which needs no API key:
# the last completed college football regular season (it ends in mid-December)
cfb_season <- as.integer(format(Sys.Date(), "%Y")) -
(format(Sys.Date(), "%m-%d") < "12-15")
cfb_fpi <- cfbfastR::espn_ratings_fpi(year = cfb_season) |>
# FPI arrives as text; sort it as a number
mutate(fpi = as.numeric(fpi)) |>
arrange(desc(fpi)) |>
select(team = team_abbreviation, fpi, w, l) |>
slice_head(n = 25)The table below uses a snapshot of this call taken on October 04, 2026 (cfbfastR 3.0.0), because ESPN’s API is not called when this site is built.
NHL skater stats (fastRhockey)
# the last completed regular season (named for the year it ends, in mid-April)
nhl_season <- as.integer(format(Sys.Date(), "%Y")) -
(format(Sys.Date(), "%m-%d") < "04-20")
nhl_skaters <- fastRhockey::nhl_stats_skaters(
season = paste0(nhl_season - 1, nhl_season),
limit = 20
) |>
# a player traded mid-season lists every team; keep his last
mutate(team = sub(".*,\\s*", "", team_abbrevs)) |>
select(skater_full_name, team, player_id, goals, assists, points)The table below uses a snapshot of this call taken on October 04, 2026 (fastRhockey 1.0.0), because the NHL Stats API is not called when this site is built.
reactable(
nhl_skaters,
columns = list(
team = colDef(cell = reactable_sdv_logos(league = "nhl", height = 28), html = TRUE),
player_id = colDef(
name = "Headshot",
# NHL API player ids: the NHL's own image CDN
cell = reactable_sdv_headshots(league = "nhl", id_system = "league", height = 40),
html = TRUE
)
)
)Integration with reactablefmtr
These helpers compose cleanly with reactablefmtr,
whose embed_img() draws a column that already holds image
URLs:
library(reactablefmtr)
standings |>
mutate(logo = sdv_logo_url(team_abbr, league = "nfl")) |>
select(logo, team_abbr, wins, losses, pct) |>
reactable(
columns = list(
logo = colDef(
name = "",
# embed_img() writes no alt: give each logo its team abbreviation
cell = function(value, index, name) {
htmltools::tagQuery(embed_img(height = 28)(value, index, name))$
find("img")$
addAttrs(alt = standings$team_abbr[[index]])$
allTags()
}
),
pct = colDef(format = colFormat(digits = 3))
)
)sdv_logo_url() gives the URL the sdvplotR helpers draw:
the SportsDataverse logo archive’s copy of the mark, an immutable
content-addressed file
(.../sha256/<ab>/<sha256>.png), so a table
built today still renders when ESPN drops or replaces a file. For the
variant resolution, the aliases and the season marks inside the table
itself, prefer the reactable_sdv_* helpers; their
<img> tags also swap to the primary logo in the
browser if a variant’s file ever fails to load.
Best practices
- Cache reactable outputs in Shiny apps — image-heavy tables can be slow to render on first load.
-
Use
variant = "dark"when the table sits on a dark background. -
Pair logos with team-colored bars
(
reactable_sdv_team_color_bar) for scannable leaderboards. -
Set
heightconsistently across a single table to keep rows aligned. -
Pre-validate teams with
sdv_resolve()before passing to reactable — it avoids silent fallback images.
Related SportsDataverse packages
- sdvplot - the Python plotting package
- sdvplot-js - the JavaScript / TypeScript plotting packages
- cfbfastR - college football
- hoopR - men’s basketball
- wehoop - women’s basketball
- baseballr - baseball
- fastRhockey - hockey
- oddsapiR - betting odds
- sportyR - playing surfaces
- sportsdataverse-py - the Python package
- sportsdataverse-R - the R meta-package