sideview puts many tables side by side on a pan-and-zoom canvas, so you can compare them, connect columns to build joins, check those joins before you run them, and take away reproducible tidyverse code. It runs as an RStudio gadget or as an htmlwidget you can save to HTML or embed in Quarto and R Markdown.

Take the visual tour to see every feature in a screenshot.
Install from r-universe (binaries for Windows and macOS):
install.packages("sideview", repos = c("https://lauyeehow1986-hub.r-universe.dev", "https://cloud.r-project.org"))
Or install the development version from GitHub:
# install.packages("remotes")
remotes::install_github("lauyeehow1986-hub/sideview")
data.frame, tibble, table, matrix, gt, flextable, arrow tables and datasets, and duckdb connections each become a card on one canvas. Tables of 100,000+ rows scroll smoothly.Ctrl+K to find a card or column, and double-click a card title to zoom to it.dplyr joins, including multi-key joins.sv_suggest_mappings() proposes likely keys from name, type and value overlap.sv_join_diagnostics()) show match rates both ways, unmatched keys, 1:1 / 1:m / m:m relationships, rows per join type, and NA, spacing or case problems in keys.relationship and unmatched checks.%>% / |> choice. Sorts and filters run on the full data in R, not just the rows on screen.sv_detect_discrepancies(), sv_auto_clean_rules()).sv_column_profile()).sv_codebook(), CSV or XLSX).sideview(..., state = "canvas.json"), sv_save_state(), sv_load_state()).sv_theme() or a bslib::bs_theme(). Press ? on the canvas for every shortcut.library(sideview)
patients <- data.frame(
patient_id = c("P01", "P02", "P03", "P04"),
age = c(45, 52, 38, 61),
sex = c("Male", "Female", "M", "Fem")
)
visits <- data.frame(
patient_id = c("P01", "P02", "P03", "P05"),
systolic_bp = c(120, 140, 115, 130)
)
# 1. Open the canvas (RStudio dialog by default; also viewer = "pane" or "browser")
if (interactive()) {
sideview(patients, visits)
}
# 2. Check a join before you run it
sv_join_diagnostics(patients, visits, by = "patient_id",
x_name = "patients", y_name = "visits")
# 3. Build a data dictionary across tables
sv_codebook(patients, visits)
# 4. Share the canvas as a standalone HTML file
save_sideview(sideview_widget(patients, visits), file = "study_overview.html")
To embed the canvas in Quarto or R Markdown, put sideview_widget(patients, visits, height = "650px") in a chunk.
sideviewUI() and sideviewServer() put an R-backed canvas into any Shiny app, several per page. Sorting, filtering, lazy arrow/duckdb tables, the join check and the column inspector run in R, as in the gadget. The server adds, replaces and removes tables through a proxy and reads back what the user built:
library(shiny)
library(sideview)
ui <- fluidPage(
actionButton("refresh", "Refresh labs"),
sideviewUI("review", height = "560px"),
verbatimTextOutput("code")
)
server <- function(input, output, session) {
px <- sideviewServer("review", cards = list(visits = visits, labs = labs_now()))
observeEvent(input$refresh, {
px <- sideviewProxy("review")
sv_replace_card(px, "labs", labs_now())
sv_highlight(px, "labs", "lab_date")
})
output$code <- renderText(px$code())
}
shinyApp(ui, server)
A replace keeps the card where it is; steps that name a column the new table lacks are disabled, not deleted, and one toast says so. px$code(), px$pipeline(), px$state() and px$cards() are reactives. Insert Script, Run & Exit, Run in R and Run Console are off in a module, because any visitor to a deployed app could forge the code they would run. $code() is composed in the browser: treat it as untrusted text to show, save or review, and never eval() it unattended. A complete app with two canvases is in system.file("examples/proxy", package = "sideview").
sideview is a research and education tool for exploring and preparing data. It is not medical device software and is not intended for clinical decisions.