Clean, predictable figures for medical and data science, built on 'ggplot2'. One frame for titles, legends, panels and captions, one type scale, colour-vision checked palettes, and print-ready files. The Python package of the same name draws the same figures from the same design tokens, so an R figure and a Python figure in one manuscript match.
install.packages("hairline")
R 4.3 or later with 'ggplot2' 4.0 or later. The bundled font, Hairline Sans, needs no installation for PNG,
SVG and HTML; for PDF on macOS without XQuartz, run hl_install_font() once.

Most studies need the same few results: a cohort description, a comparison between groups, effects, events
over time. hl_study() takes a data frame once, with the role of each column, checks the data, and returns
each result as a figure, a table, the key values and a sentence for the text.
library(hairline)
set.seed(1)
cohort <- data.frame(arm = rep(c("Standard care", "Early mobilisation"), each = 40),
age = round(rnorm(80, 64, 12)), sex = sample(c("Female", "Male"), 80, TRUE),
months = round(rexp(80, 0.05), 1), died = rbinom(80, 1, 0.4))
study <- hl_study(cohort, group = "arm", time = "months", event = "died",
units = c(age = "years", months = "months"))
table_one <- hl_describe(study) # Table 1: median (IQR) and n (%) per group
table_one$table # a data frame
age <- hl_compare(study, "age") # spread rows per group, Wilcoxon rank-sum
age$text # "Age (years), median (IQR): ... (Wilcoxon rank-sum, p = ...)."
survival <- hl_survival(study) # Kaplan-Meier with numbers at risk and the log-rank test
hl_save(survival$figure, "survival") # survival.pdf and survival.png
Printing a study lists its checks: missing values, duplicate ids, categories that differ only in case, small groups, numeric columns without a unit. Errors stop the analysis; warnings name the fix.
Each chart is one call with the data and its column names, and returns a ggplot in the hairline frame:
hl_bars(), hl_scatter(), hl_timeseries(), hl_spread(), hl_raincloud(), hl_ridgeline(),
hl_kaplan_meier(), hl_forest(), hl_dotplot(), hl_funnel(), hl_correlation(), hl_donut(),
hl_partition(), hl_sankey(), hl_chord(), hl_arcs(), hl_venn(), hl_prisma() and
hl_risk_of_bias().
admissions <- data.frame(diagnosis = c("Sepsis", "Sepsis", "Trauma", "Cardiac", "Cardiac"),
sex = c("Female", "Male", "Male", "Female", "Male"))
figure <- hl_bars(admissions, "diagnosis", by = "sex", subtitle = "Admissions, 2025")
hl_save(figure, "admissions", formats = c("pdf", "png", "svg", "html"))
For anything else, theme_hairline() puts the same frame around your own ggplot:
library(ggplot2)
figure <- ggplot(mtcars, aes(wt, mpg)) + geom_point() +
labs(title = "Fuel use by weight", x = "Weight (1000 lb)", y = "Miles per gallon") +
theme_hairline()
hl_save(figure, "fuel", size = "single")
Groups take a palette's colours in order, so two groups always get the same two colours and a group keeps its
colour in every chart. hl_palettes() lists the six palettes, the purpose sets (two groups, greyscale print,
risk of bias) and the ramps for amounts; hl_swatches() draws them; hl_use(palette = "calm") changes the
default; hl_use(colors = c("Unit B" = "#e79d01")) pins a colour to a name; scale_fill_hairline_c("blues")
gives a ramp to your own ggplots. ?hairline_colours has the details.
hl_save() writes PDF, PNG and TIFF (600 dpi by default), SVG (text kept as text) and self-contained
interactive HTML with tooltips. Files carry the figure's title and subtitle as their description, and no
timestamps.
hl_use(brand = "_brand.yml") applies an institution's colours, fonts and sizes from a
brand.yml file; several files layer from institution to document.hl_use(colors = c("Unit B" = "#e79d01")) pins a colour to a name across every chart.hl_check_palette() reports the numbers.hl_use(scheme = "dark", accent = "#0b6e4f") draws on a dark surface with an accent for single series.More figures, Python and R side by side, and the colour guide: https://github.com/schradern/hairline.

Nikolas Schrader (hi@nikolasschrader.com), ORCID 0009-0001-4616-3804.
If hairline helped you make the figures in your work, please cite it; citation("hairline") prints the
reference:
@Manual{schrader2026hairline,
title = {hairline: Clean, Predictable Figures for Medical and Data Science},
author = {Nikolas Schrader},
year = {2026},
note = {R package version 0.1.0},
doi = {10.5281/zenodo.23456212},
}
MIT. The bundled Hairline Sans font is a modified Public Sans (SIL Open Font License 1.1); see
inst/COPYRIGHTS.