The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
All examples below are complete Shiny apps using
bslib with Bootstrap 5. Copy, paste, and run.
The minimal DT2 Shiny app uses dt2_output() in the UI
and render_dt2() in the server. DT2 tables integrate
naturally with bslib cards and Bootstrap 5 themes — the
table inherits the page theme automatically:
library(shiny)
library(bslib)
library(DT2)
ui <- page_fillable(
theme = bs_theme(version = 5, bootswatch = "flatly"),
padding = "1rem",
card(
card_header("DT2 in Shiny"),
card_body(dt2_output("my_table"))
)
)
server <- function(input, output, session) {
output$my_table <- render_dt2({
dt2(iris, options = list(pageLength = 10))
})
}
shinyApp(ui, server)Every DT2 table pushes its state (order,
search, page, selected rows) to
input$<id>_state automatically:
library(shiny)
library(bslib)
library(DT2)
ui <- page_fillable(
theme = bs_theme(version = 5, bootswatch = "flatly"),
padding = "1rem",
card(
card_header("Table State"),
card_body(dt2_output("tbl"))
),
card(
card_header("Current state"),
card_body(verbatimTextOutput("state_info"))
)
)
server <- function(input, output, session) {
output$tbl <- render_dt2({
dt2(iris, options = list(pageLength = 10))
})
output$state_info <- renderPrint({
state <- input$tbl_state
if (is.null(state)) return("(interact with the table)")
cat("Reason:", state$reason, "\n")
cat("Page:", state$page$page + 1, "of", state$page$pages, "\n")
cat("Search:", if (nzchar(state$search)) state$search else "(none)", "\n")
cat("Order:", paste(state$order, collapse = ", "), "\n")
})
}
shinyApp(ui, server)Use dt2_proxy() to update a table without re-rendering
it. This is essential for responsive dashboards: you can replace the
data, change the page, adjust sorting, or update the search filter — all
without the flicker of a full re-render:
library(shiny)
library(bslib)
library(DT2)
ui <- page_sidebar(
theme = bs_theme(version = 5, bootswatch = "flatly"),
title = "Proxy Demo",
sidebar = sidebar(
actionButton("refresh", "Refresh data", class = "btn-sm btn-outline-primary w-100 mb-2"),
actionButton("go_last", "Go to last page", class = "btn-sm btn-outline-primary w-100 mb-2"),
actionButton("sort_sl", "Sort Sepal.Length \u2193", class = "btn-sm btn-outline-primary w-100 mb-2"),
hr(),
textInput("search_text", "Search", placeholder = "e.g. setosa"),
actionButton("search_btn", "Apply", class = "btn-sm btn-primary w-100")
),
card(
card_body(dt2_output("tbl"))
)
)
server <- function(input, output, session) {
output$tbl <- render_dt2({
dt2(iris, options = list(pageLength = 10))
})
proxy <- dt2_proxy("tbl")
observeEvent(input$refresh, {
new_data <- iris[sample(nrow(iris), 50), ]
dt2_replace_data(proxy, new_data)
})
observeEvent(input$go_last, {
dt2_proxy_page(proxy, "last")
})
observeEvent(input$sort_sl, {
dt2_proxy_order(proxy, c("Sepal.Length", "desc"),
columns = names(iris))
})
observeEvent(input$search_btn, {
dt2_proxy_search(proxy, input$search_text)
})
}
shinyApp(ui, server)Use virtual scrolling instead of pagination for large datasets. The
Scroller extension renders only the visible rows inside a fixed-height
container; more rows load seamlessly as the user scrolls. Combined with
deferRender = TRUE, this keeps memory usage low even for
tens of thousands of rows:
library(shiny)
library(bslib)
library(DT2)
ui <- page_fillable(
theme = bs_theme(version = 5, bootswatch = "flatly"),
padding = "1rem",
card(
card_header("Scroller \u2014 10 000 rows"),
card_body(dt2_output("tbl"))
)
)
server <- function(input, output, session) {
big <- data.frame(
id = 1:10000,
value = round(rnorm(10000), 3),
group = sample(LETTERS[1:5], 10000, replace = TRUE)
)
output$tbl <- render_dt2({
dt2(big, options = list(
scroller = TRUE,
scrollY = 400,
deferRender = TRUE,
buttons = list("copy", "csv"),
layout = list(
topStart = list(search = list(placeholder = "Filter...")),
topEnd = "buttons"
)
))
})
}
shinyApp(ui, server)DataTables v2 uses layout to position toolbar elements
around the table. Each slot (topStart, topEnd,
bottomStart, bottomEnd) accepts a string,
list, or NULL. This example rearranges search, buttons,
info, and pagination into non-default positions:
library(shiny)
library(bslib)
library(DT2)
ui <- page_fillable(
theme = bs_theme(version = 5, bootswatch = "flatly"),
padding = "1rem",
card(
card_header("Custom Layout"),
card_body(dt2_output("tbl"))
)
)
server <- function(input, output, session) {
output$tbl <- render_dt2({
dt2(iris, options = list(
pageLength = 10,
layout = list(
topStart = "pageLength",
topEnd = list(
buttons = list("copy", "csv", "excel"),
search = list(placeholder = "Search...")
),
bottomStart = "info",
bottomEnd = "paging"
)
))
})
}
shinyApp(ui, server)DataTables v2 configures pagination through the layout
option using paging as a named list, replacing the
deprecated pagingType parameter. This example shows three
common configurations side by side: simple navigation (prev/next only),
full navigation with all elements, and no pagination at all.
library(shiny)
library(bslib)
library(DT2)
ui <- page_fillable(
theme = bs_theme(version = 5, bootswatch = "flatly"),
padding = "1rem",
layout_columns(
col_widths = c(6, 6),
card(
card_header("Simple (prev / next only)"),
card_body(dt2_output("tbl_simple"))
),
card(
card_header("Full (first / 1 2 3 / last)"),
card_body(dt2_output("tbl_full"))
)
),
card(
card_header("No pagination"),
card_body(dt2_output("tbl_none"))
)
)
server <- function(input, output, session) {
output$tbl_simple <- render_dt2({
dt2(iris, options = list(
pageLength = 5,
layout = list(bottomEnd = list(paging = list(
type = "simple"
)))
))
})
output$tbl_full <- render_dt2({
dt2(iris, options = list(
pageLength = 5,
layout = list(bottomEnd = list(paging = list(
firstLast = TRUE, previousNext = TRUE, numbers = TRUE
)))
))
})
output$tbl_none <- render_dt2({
dt2(iris[1:15, ], options = list(paging = FALSE))
})
}
shinyApp(ui, server)For very large datasets (100k+ rows), use server-side processing. Only the visible page is sent to the browser — sorting, filtering, and pagination happen on the R server. This avoids sending the entire dataset to the client, dramatically improving initial load time:
library(shiny)
library(bslib)
library(DT2)
ui <- page_fillable(
theme = bs_theme(version = 5, bootswatch = "flatly"),
padding = "1rem",
card(
card_header("Server-Side Processing \u2014 100k rows"),
card_body(dt2_output("tbl"))
)
)
server <- function(input, output, session) {
large_data <- data.frame(
id = seq_len(100000),
value = round(rnorm(100000), 4),
group = sample(LETTERS[1:5], 100000, replace = TRUE)
)
dt2_bind_server("tbl", large_data)
output$tbl <- render_dt2({
dt2(large_data, options = list(
columns = names(large_data),
pageLength = 25,
server_side = TRUE,
language = list(info = "Showing _START_ to _END_ of _TOTAL_ rows")
))
})
}
shinyApp(ui, server)A common dashboard pattern: let the user choose a dataset and a theme
preset from the sidebar. The table re-renders reactively whenever either
input changes. This demonstrates how DT2 integrates naturally with
bslib’s page_sidebar() layout:
library(shiny)
library(bslib)
library(DT2)
ui <- page_sidebar(
theme = bs_theme(version = 5, bootswatch = "flatly"),
title = "DT2 + bslib",
sidebar = sidebar(
selectInput("dataset", "Dataset",
c("iris", "mtcars", "airquality")),
selectInput("theme_preset", "Theme preset",
c("default", "clean", "minimal", "compact"))
),
card(
card_header("Table"),
card_body(dt2_output("tbl"))
)
)
server <- function(input, output, session) {
output$tbl <- render_dt2({
data <- switch(input$dataset,
iris = iris,
mtcars = mtcars,
airquality = airquality
)
dt2(data,
theme = input$theme_preset,
options = list(pageLength = 10))
})
}
shinyApp(ui, server)This Shiny app demonstrates most DT2 features together: ColumnControl dropdowns, export buttons with spacer, custom JS renderers (links, flags, progress bars, colored currency), and full pt-BR translation.
Copy and run it directly — it is also available at
system.file("examples/app_complete.R", package = "DT2").
library(jsonlite)
library(dplyr)
library(tibble)
library(lubridate)
# ── Sample data (57 employees) ────────────────────────────────────────────────
json_url <- "https://raw.githubusercontent.com/StrategicProjects/DT2/main/inst/examples/employees.json"
# For offline use, the same JSON is bundled in inst/examples/employees.json
json_txt <- '{
"data": [
{"name":"Tiger Nixon","position":"System Architect","salary":"320800","start_date":"2011-04-25","office":"Edinburgh","extn":"5421"},
{"name":"Garrett Winters","position":"Accountant","salary":"170750","start_date":"2011-07-25","office":"Tokyo","extn":"8422"},
{"name":"Ashton Cox","position":"Junior Technical Author","salary":"86000","start_date":"2009-01-12","office":"San Francisco","extn":"1562"},
{"name":"Cedric Kelly","position":"Senior JavaScript Developer","salary":"433060","start_date":"2012-03-29","office":"Edinburgh","extn":"6224"},
{"name":"Airi Satou","position":"Accountant","salary":"162700","start_date":"2008-11-28","office":"Tokyo","extn":"5407"},
{"name":"Brielle Williamson","position":"Integration Specialist","salary":"372000","start_date":"2012-12-02","office":"New York","extn":"4804"},
{"name":"Herrod Chandler","position":"Sales Assistant","salary":"137500","start_date":"2012-08-06","office":"San Francisco","extn":"9608"},
{"name":"Rhona Davidson","position":"Integration Specialist","salary":"327900","start_date":"2010-10-14","office":"Tokyo","extn":"6200"},
{"name":"Colleen Hurst","position":"JavaScript Developer","salary":"205500","start_date":"2009-09-15","office":"San Francisco","extn":"2360"},
{"name":"Sonya Frost","position":"Software Engineer","salary":"103600","start_date":"2008-12-13","office":"Edinburgh","extn":"1667"}
]
}'
df <- fromJSON(json_txt, flatten = TRUE)$data %>%
as_tibble() %>%
mutate(
salary = as.numeric(salary),
extn = as.integer(extn),
start_date = ymd(start_date)
)
# ── Shiny App ─────────────────────────────────────────────────────────────────
library(shiny)
library(bslib)
library(DT2)
library(htmlwidgets)
ui <- page_sidebar(
theme = bs_theme(version = 5, bootswatch = "spacelab"),
title = "DT2 — Complete Example",
sidebar = sidebar(
h5("Features"),
tags$ul(
tags$li("ColumnControl (order + search dropdowns)"),
tags$li("Export buttons with spacer"),
tags$li("Custom JS renderers"),
tags$li("pt-BR translation")
)
),
# Flag sprites
tags$head(
tags$link(
rel = "stylesheet", type = "text/css",
href = "https://cdn.jsdelivr.net/gh/lafeber/world-flags-sprite/stylesheets/flags32-both.css"
),
tags$style(HTML("
.f32 .flag { display:inline-block; width:32px; height:32px;
vertical-align:middle; margin-right:6px; }
table.dataTable tbody td { vertical-align: middle; }
"))
),
card(
card_header("Employee Table"),
card_body(dt2_output("tbl", height = "auto"))
)
)
server <- function(input, output, session) {
output$tbl <- render_dt2({
# ── JS Renderers ──────────────────────────────────────────
office_js <- JS("
function(data, type) {
if (type !== 'display') return data;
var cc = {Argentina:'ar', Edinburgh:'_Scotland', London:'_England',
'New York':'us', 'San Francisco':'us', Sydney:'au', Tokyo:'jp'};
var flag = cc[data] || '';
return '<span class=\"flag ' + flag + '\"></span> ' + data;
}
")
salary_js <- JS("
(function() {
var nfmt = DataTable.render.number('.', ',', 2, 'R$ ');
return function(data, type) {
var txt = nfmt.display(data);
if (type !== 'display') return txt;
var c = data < 250000 ? 'red' : data < 500000 ? 'orange' : 'green';
return '<span style=\"color:' + c + '\">' + txt + '</span>';
};
})()
")
extn_js <- JS("
function(data, type) {
return type === 'display'
? '<progress value=\"' + data + '\" max=\"9999\"></progress>'
: data;
}
")
# ── Options ───────────────────────────────────────────────
opts <- list(
pageLength = 10,
lengthMenu = c(10, 25, -1),
columns = names(df),
layout = list(
topStart = "pageLength",
topEnd = list(
buttons = list(
list(extend = "copyHtml5", text = "Copiar"),
list(extend = "csvHtml5"),
list(extend = "excelHtml5"),
list(extend = "spacer", style = "bar"),
list(extend = "colvis", text = "Colunas")
),
search = list(placeholder = "")
),
bottomEnd = "paging"
),
columnControl = list(
target = 0,
content = list("order", "searchDropdown", list(
list(extend = "orderAsc", text = "Ordem crescente"),
list(extend = "orderDesc", text = "Ordem decrescente"),
"spacer",
list(extend = "colVisDropdown", text = "Selecionar colunas")
))
),
ordering = list(indicators = FALSE, handler = FALSE),
columnDefs = list(
list(targets = which(names(df) == "office") - 1L,
className = "f32", render = office_js),
list(targets = which(names(df) == "salary") - 1L,
className = "dt-body-right", render = salary_js),
list(targets = which(names(df) == "extn") - 1L,
render = extn_js)
),
language = list(
lengthMenu = "Mostrar _MENU_",
search = "Buscar",
info = "Mostrando _START_ a _END_ de _TOTAL_ registros",
infoEmpty = "Nenhum registro",
zeroRecords = "Nenhum registro encontrado",
emptyTable = "Nenhum dado disponível",
decimal = ",", thousands = ".", infoThousands = ".",
lengthLabels = list(`10` = "10", `25` = "25", `-1` = "Todas"),
paginate = list(first="«", previous="‹", `next`="›", last="»"),
buttons = list(
copyTitle = "Copiado!",
copySuccess = list(`_` = "%d linhas copiadas", `1` = "1 linha copiada")
),
columnControl = list(
orderAsc = "Crescente", orderDesc = "Decrescente",
searchDropdown = "Pesquisar", colVisDropdown = "Colunas",
searchClear = "Limpar",
search = list(
text = list(contains="Contém", starts="Começa por",
ends="Termina em", equal="Igual a"),
number = list(greater="Maior que", less="Menor que",
equal="Igual a")
)
)
)
)
dt2(df,
compact = TRUE, striped = TRUE, hover = TRUE,
font_scale = 0.85, responsive = FALSE,
options = opts)
})
}
shinyApp(ui, server)These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.