Introduction

This code through explores the kableExtra package and how it can transform standard R data frames into executive-ready HTML tables.


Content Overview

Specifically, we’ll explain and demonstrate how to take a custom dataset of go-to-market recruitment metrics and visually upgrade it using HTML formatting, grouped rows, and highlighted cells.


Why You Should Care

In talent acquisition, pulling the numbers is only half the battle. If you present raw, unformatted R console output to hiring managers or executive leadership, you risk losing their attention and obscuring key insights.

Using a package like kableExtra allows you to transform messy data into clean, professional presentation tables. By grouping roles by department, cleanly formatting currency, and using color to highlight critical metrics—such as identifying the role with the highest cost-per-hire—you guide your stakeholders directly to the story your data is telling.


Learning Objectives

Specifically, you’ll learn how to:

Generate a basic HTML table using kbl().

Apply a professional theme using kable_styling().

Format specific columns natively in R before piping to a table.

Group rows by category using pack_rows().

Highlight specific rows to draw attention to key data points using row_spec().



Demonstration

Here, we’ll walk through how to build a recruitment dataset from scratch and incrementally style it using kableExtra.


Dataset Overview

This tutorial utilizes a custom go-to-market recruitment dataset tracking time-to-fill and cost-per-hire metrics across departments.

Basic Example

A basic example shows how standard markdown renders a table. If we just print it or use pander(), it gets the job done, but it looks a bit plain:

# Create custom recruiting dataset
gtm_metrics <- data.frame(
  Department = c("Sales", "Sales", "Sales", "Marketing", "Marketing"),
  Role = c("Account Executive", "Sales Development Rep", "Solutions Architect", "Product Marketing Mgr", "Growth Marketer"),
  Time_to_Fill_Days = c(45, 28, 55, 60, 40),
  Cost_Per_Hire = c(8500, 4200, 10500, 9200, 6800)
)

# Render basic table
pander(gtm_metrics)
Department Role Time_to_Fill_Days Cost_Per_Hire
Sales Account Executive 45 8500
Sales Sales Development Rep 28 4200
Sales Solutions Architect 55 10500
Marketing Product Marketing Mgr 60 9200
Marketing Growth Marketer 40 6800


Advanced Examples

More specifically, this can be used for applying professional design themes. By piping our table output into kable_styling(), we can add striped rows, hover effects, and control table width to ensure it looks clean and polished.

gtm_metrics %>%
  kbl() %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = FALSE)
Department Role Time_to_Fill_Days Cost_Per_Hire
Sales Account Executive 45 8500
Sales Sales Development Rep 28 4200
Sales Solutions Architect 55 10500
Marketing Product Marketing Mgr 60 9200
Marketing Growth Marketer 40 6800


What’s more, it can also be used for formatting specific columns and grouping data. We can format the currency natively in R, clean up the column headers, and use pack_rows() to organize the roles cleanly by department.

gtm_metrics %>%
  mutate(Cost_Per_Hire = paste0("$", formatC(Cost_Per_Hire, format="f", digits=0, big.mark=","))) %>%
  select(Role, Time_to_Fill_Days, Cost_Per_Hire) %>%
  kbl(col.names = c("Role", "Time to Fill (Days)", "Cost Per Hire"), align = c("l", "c", "c")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  pack_rows("Sales", 1, 3) %>%
  pack_rows("Marketing", 4, 5)
Role Time to Fill (Days) Cost Per Hire
Sales
Account Executive 45 $8,500
Sales Development Rep 28 $4,200
Solutions Architect 55 $10,500
Marketing
Product Marketing Mgr 60 $9,200
Growth Marketer 40 $6,800


Most notably, it’s valuable for drawing immediate executive attention to critical outliers. By combining data formatting with row_spec(), we can programmatically highlight high-cost anomalies—such as calling out the Solutions Architect role—directly in the visual presentation.

gtm_metrics %>%
  mutate(Cost_Per_Hire = paste0("$", formatC(Cost_Per_Hire, format="f", digits=0, big.mark=","))) %>%
  select(Role, Time_to_Fill_Days, Cost_Per_Hire) %>%
  kbl(col.names = c("Role", "Time to Fill (Days)", "Cost Per Hire"), align = c("l", "c", "c")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  pack_rows("Sales", 1, 3) %>%
  pack_rows("Marketing", 4, 5) %>%
  
  # Highlight the 3rd row (Solutions Architect) to flag the highest cost outlier
  row_spec(3, bold = TRUE, color = "black", background = "#F1C40F")
Role Time to Fill (Days) Cost Per Hire
Sales
Account Executive 45 $8,500
Sales Development Rep 28 $4,200
Solutions Architect 55 $10,500
Marketing
Product Marketing Mgr 60 $9,200
Growth Marketer 40 $6,800



Further Resources

Learn more about the kableExtra package and advanced table formatting with the following official guides:




Works Cited

This code-through references and cites the following technical documentation and packages:


  • Hao Zhu (2024). kableExtra: Construct Complex Table with ‘knitr::kable’ + Pipe. CRAN Package Documentation. CRAN: kableExtra

  • Yihui Xie, J. J. Allaire, and Garrett Grolemund (2020). R Markdown: The Definitive Guide. Chapman and Hall/CRC. Bookdown Reference

  • R Core Team (2026). R: A Language and Environment for Statistical Computing. R Foundation for Statistical Computing, Vienna, Austria. R-project.org