This code through explores the kableExtra package and how it can transform standard R data frames into executive-ready HTML tables.
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.
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.
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().
Here, we’ll walk through how to build a recruitment dataset from
scratch and incrementally style it using kableExtra.
This tutorial utilizes a custom go-to-market recruitment dataset
tracking time-to-fill and cost-per-hire metrics across departments.
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 |
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 |
Learn more about the kableExtra package and advanced
table formatting with the following official guides:
Resource I: Create Awesome HTML Tables with knitr::kable and kableExtra
Resource II: R Markdown: The Definitive Guide - kableExtra Chapter
Resource III: Official RStudio R Markdown Gallery
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