Skip to contents
On this page

sdvplotR has two kinds of gt helpers, and this article uses both on one table.

  • The team layer turns team keys into logos, headshots and team colors (gt_sdv_logos(), gt_sdv_headshots(), gt_merge_stack_team_color()). It resolves every abbreviation, alias and relocated franchise through sdv_resolve().
  • The table toolkit, ported from Andrew Weatherman’s gtUtils, handles the editorial side: cut lines, colored ranks, captions, legends, grids of tables, themes, and saving images sized for posting.

Every one of these functions takes a gt table and returns one, so they chain in any order. Having both in one package means one install, and one set of conventions for how a sports table is built.

The data

The final 2025 standings. nflseedR::nfl_standings() computes them from nflverse’s schedule, read with nflreadr, NFL tiebreakers included; its conf_rank is each team’s place in its conference, so ranks 1-7 are the playoff seeds. The seeds come first in seed order, followed by the rest.

standings <- nflseedR::nfl_standings(nflreadr::load_schedules(2025), verbosity = "NONE")

conference_standings <- function(conference) {
  s <- standings[standings$conf == conference, ]
  s <- s[order(s$conf_rank), ]
  data.frame(
    team = s$team,
    seed = ifelse(s$conf_rank <= 7, s$conf_rank, NA),
    w = s$true_wins,
    l = s$losses,
    pct = s$win_pct
  )
}

afc <- conference_standings("AFC")

Step by step

Start with logos, labels and a box-score win percentage (.765, not 0.765).

base <- function(df, conference) {
  gt(df) |>
    gt_sdv_logos(columns = "team", league = "nfl", height = 24) |>
    cols_label(team = "", seed = "Seed", w = "W", l = "L", pct = "Win pct") |>
    sub_missing(columns = "seed", missing_text = "") |>
    fmt(columns = "pct", fns = function(x) sub("^0", "", sprintf("%.3f", x))) |>
    tab_header(paste(conference, "playoff picture"), "Final 2025 standings")
}
base(afc, "AFC")
AFC playoff picture
Final 2025 standings
Seed W L Win pct
Denver Broncos 1 14 3 .824
New England Patriots 2 14 3 .824
Jacksonville Jaguars 3 13 4 .765
Pittsburgh Steelers 4 10 7 .588
Houston Texans 5 12 5 .706
Buffalo Bills 6 12 5 .706
Los Angeles Chargers 7 11 6 .647
Indianapolis Colts
8 9 .471
Baltimore Ravens
8 9 .471
Miami Dolphins
7 10 .412
Cincinnati Bengals
6 11 .353
Kansas City Chiefs
6 11 .353
Cleveland Browns
5 12 .294
Las Vegas Raiders
3 14 .176
New York Jets
3 14 .176
Tennessee Titans
3 14 .176

A standings table is read around one question: who is in? gt_cutline() draws the answer as a line after the seventh row, with a label, so readers don’t have to count seeds.

base(afc, "AFC") |>
  gt_cutline(after = 7, label = "Playoff line")
AFC playoff picture
Final 2025 standings
Seed W L Win pct
Denver Broncos 1 14 3 .824
New England Patriots 2 14 3 .824
Jacksonville Jaguars 3 13 4 .765
Pittsburgh Steelers 4 10 7 .588
Houston Texans 5 12 5 .706
Buffalo Bills 6 12 5 .706
Los Angeles Chargers 7 11 6 .647
Indianapolis Colts
8 9 .471
Baltimore Ravens
8 9 .471
Miami Dolphins
7 10 .412
Cincinnati Bengals
6 11 .353
Kansas City Chiefs
6 11 .353
Cleveland Browns
5 12 .294
Las Vegas Raiders
3 14 .176
New York Jets
3 14 .176
Tennessee Titans
3 14 .176

gt_color_ranks() colors a column by its values, which shows where the wins cluster faster than reading the numbers; reverse = TRUE puts the most wins at the green end. The cut line’s label sits in the row below the line and clears that row’s cell colors, so with colored cells, keep the line unlabeled and say what it means in the caption. gt_538_caption() separates that note from the source line. gt_theme_sdv() goes last so it styles everything added before it, and density is passed through for the saved version below.

playoff_table <- function(df, conference, density = "comfortable") {
  base(df, conference) |>
    gt_cutline(after = 7) |>
    gt_color_ranks(columns = "w", reverse = TRUE) |>
    gt_538_caption(
      top_caption = "Above the line: playoff teams.",
      bottom_caption = "Data: nflverse via nflreadr and nflseedR"
    ) |>
    gt_theme_sdv(density = density)
}
playoff_table(afc, "AFC")
AFC playoff picture
Final 2025 standings
1 Seed1 W1 L1 Win pct1
Denver Broncos 1 14 3 .824
New England Patriots 2 14 3 .824
Jacksonville Jaguars 3 13 4 .765
Pittsburgh Steelers 4 10 7 .588
Houston Texans 5 12 5 .706
Buffalo Bills 6 12 5 .706
Los Angeles Chargers 7 11 6 .647
Indianapolis Colts
8 9 .471
Baltimore Ravens
8 9 .471
Miami Dolphins
7 10 .412
Cincinnati Bengals
6 11 .353
Kansas City Chiefs
6 11 .353
Cleveland Browns
5 12 .294
Las Vegas Raiders
3 14 .176
New York Jets
3 14 .176
Tennessee Titans
3 14 .176
1 Above the line: playoff teams.
Data: nflverse via nflreadr and nflseedR

Both conferences at once

gt_grid() lays several finished tables out under one shared title and source line. Each table keeps its own theme and cut line; a note that applies to both goes in the grid’s caption, so each table’s own caption stays short and the tables stay narrow enough to sit side by side.

nfc <- conference_standings("NFC")

gt_grid(
  list(playoff_table(afc, "AFC"), playoff_table(nfc, "NFC")),
  ncol = 2,
  title = "The 2025 NFL playoff field",
  subtitle = "Final regular-season standings",
  caption = "Seeds 1-4 won their division; 5-7 are wild cards.",
  title_style = list(font = "Chivo", weight = 800)
)
The 2025 NFL playoff field
Final regular-season standings
AFC playoff picture
Final 2025 standings
1 Seed1 W1 L1 Win pct1
Denver Broncos 1 14 3 .824
New England Patriots 2 14 3 .824
Jacksonville Jaguars 3 13 4 .765
Pittsburgh Steelers 4 10 7 .588
Houston Texans 5 12 5 .706
Buffalo Bills 6 12 5 .706
Los Angeles Chargers 7 11 6 .647
Indianapolis Colts
8 9 .471
Baltimore Ravens
8 9 .471
Miami Dolphins
7 10 .412
Cincinnati Bengals
6 11 .353
Kansas City Chiefs
6 11 .353
Cleveland Browns
5 12 .294
Las Vegas Raiders
3 14 .176
New York Jets
3 14 .176
Tennessee Titans
3 14 .176
1 Above the line: playoff teams.
Data: nflverse via nflreadr and nflseedR
NFC playoff picture
Final 2025 standings
1 Seed1 W1 L1 Win pct1
Seattle Seahawks 1 14 3 .824
Chicago Bears 2 11 6 .647
Philadelphia Eagles 3 11 6 .647
Carolina Panthers 4 8 9 .471
Los Angeles Rams 5 12 5 .706
San Francisco 49ers 6 12 5 .706
Green Bay Packers 7 9 7 .559
Minnesota Vikings
9 8 .529
Detroit Lions
9 8 .529
Tampa Bay Buccaneers
8 9 .471
Atlanta Falcons
8 9 .471
Dallas Cowboys
7 9 .441
New Orleans Saints
6 11 .353
Washington Commanders
5 12 .294
New York Giants
4 13 .235
Arizona Cardinals
3 14 .176
1 Above the line: playoff teams.
Data: nflverse via nflreadr and nflseedR
Seeds 1-4 won their division; 5-7 are wild cards.

On a narrow screen the grid scrolls sideways inside its own box, so the page around it stays in place.

Posting it

gt_save_crop() saves a single table, trimmed and padded; gt_social_crop() pads it to a platform’s aspect ratio. Use the theme’s background from theme_bg so the padding blends with the table. gt_grid() saves the whole grid when you give it a file. Saving drives a headless Chrome through webshot2, so these lines are not run here.

bg <- theme_bg$bg[theme_bg$theme == "gt_theme_sdv" & theme_bg$has_style == "light"]

playoff_table(afc, "AFC", density = "social") |>
  gt_social_crop("afc-playoffs.png", aspect_ratio = "4:5", bg = bg)

gt_grid(
  list(playoff_table(afc, "AFC"), playoff_table(nfc, "NFC")),
  title = "The 2025 NFL playoff field",
  file = "playoff-field.png"
)

Where to go next

Citation

To cite the sdvplotR R package in publications, use:

BibTeX Citation

@misc{gilani_carl_lee_kay_weatherman_sdvplotR,
  author = {Gilani, Saiem and Carl, Sebastian and Lee, Jared and Kay, Camden and Weatherman, Andrew},
  title = {sdvplotR: The SportsDataverse's R Package for Multi-League Sports Plotting.},
  url = {https://sdvplotR.sportsdataverse.org},
  year = {2026}
}