html element construction for R.
hypertext provides a deterministic, framework-agnostic DSL for building html nodes and rendering them to a string.
- dependency-free. zero package imports. lightweight is the right weight, as suggested in the tinyverse philosophy.
- does not implement templating, dependency management, or widgets/framework integrations.
- heavily inspired by {htmltools}.
installation
install the stable version from CRAN:
install.packages("hypertext")or get the development version from github:
devtools::install_github("sigflux/hypertext")
quick start
page <- tag_list( doctype(), tags$html( tags$head( tags$title("hypertext") ), tags$body( tags$h1("Hello"), tags$p( class = c("lead", "mb-2"), "Server-side HTML." ), tags$input( type = "text", placeholder = "enter your nickname" ), tags$button( "Click" ) ) ) ) page #> <!DOCTYPE html> #> <html> #> <head> #> <title>hypertext</title> #> </head> #> <body> #> <h1>Hello</h1> #> <p class="lead mb-2">Server-side HTML.</p> #> <input type="text" placeholder="enter your nickname" /> #> <button>Click</button> #> </body> #> </html>
rendering
render() takes a tag tree and returns a single HTML string:
x <- tags$p( class = "lead", "hello" ) # `x` contains the tag tree class(x) #> [1] "hypertext.tag" x #> <p class="lead">hello</p> # rendering produces an HTML string: render(x) #> [1] "<p class=\"lead\">hello</p>"
you can render() directly to an html file by supplying the file parameter:
page <- tag_list( doctype(), tags$html( tags$head( tags$title("hypertext") ), tags$body( tags$h1("Hello"), tags$p( class = c("lead", "mb-2"), "Server-side HTML." ), tags$input( type = "text", placeholder = "enter your nickname" ), tags$button( "Click" ) ) ) ) render(x = page, file = "index.html")
tag lists
tag_list() groups sibling nodes without wrapping them in a parent element.
header <- tag_list( tags$h1("hello"), tags$p( class = "lead", "welcome aboard." ) ) # `header` is a "hypertext.tag.list" object: class(header) #> [1] "hypertext.tag.list" "list" header #> <h1>hello</h1> #> <p class="lead">welcome aboard.</p>
raw html
raw_html() marks a string as pre-rendered HTML so that render() outputs it verbatim, without escaping. useful for injecting inline scripts, styles, SVG markup, or any content that is already valid HTML.
tags$div( raw_html("<svg viewBox='0 0 100 100'><circle cx='50' cy='50' r='40'/></svg>") ) #> <div><svg viewBox='0 0 100 100'><circle cx='50' cy='50' r='40'/></svg></div>
doctype() is a convenience wrapper around raw_html("<!DOCTYPE html>").
custom tags
tag() creates elements for any tag name, including web components and custom elements not in the built-in tags list. it takes 3 arguments:
tag_name: name of the html element....: attributes (named) & children (unnamed).tag_type: either "normal" (default) for the standard html elements, or "void" for the self-closing elements.
content <- tag( tag_name = "calcite-action-bar", layout = "horizontal" ) content #> <calcite-action-bar layout="horizontal"></calcite-action-bar>
nest them freely with each other and with built-in tags:
page <- tags$div( class = "app", tag( tag_name = "calcite-shell", tag( tag_name = "calcite-shell-panel", slot = "panel-start", tag( tag_name = "calcite-action-bar", tag( tag_name = "calcite-action", text = "Layers", icon = "layers" ), tag( tag_name = "calcite-action", text = "Basemaps", icon = "basemap" ) ) ), tags$div(id = "map") ) ) page #> <div class="app"> #> <calcite-shell> #> <calcite-shell-panel slot="panel-start"> #> <calcite-action-bar> #> <calcite-action text="Layers" icon="layers"></calcite-action> #> <calcite-action text="Basemaps" icon="basemap"></calcite-action> #> </calcite-action-bar> #> </calcite-shell-panel> #> <div id="map"></div> #> </calcite-shell> #> </div>
for self-closing elements, set tag_type = "void":
content <- tag( tag_name = "my-icon", name = "home", tag_type = "void" ) content #> <my-icon name="home" />
usage in frameworks
ambiorix
ambiorix is the perfect example of a web framework where you will find {hypertext} useful:
library(ambiorix) library(hypertext) app <- Ambiorix$new(port = 3000L) app$get("/", function(req, res) { html <- tags$h1("hello, world!") |> render() res$send(html) }) app$get("/about", function(req, res) { html <- tag_list( tags$h1("about us"), tags$p( "minimal ", tags$strong("html construction"), " for R." ) ) |> render() res$send(html) }) app$get("/team", function(req, res) { teammates <- c("you", "me", "other") html <- tags$div( class = "team", tags$p("meet the team:"), tags$ul( lapply(teammates, tags$li) ) ) |> render() res$send(html) }) app$start()
shiny
shiny already has {htmltools} tags internally, so you do not need {hypertext} in your shiny apps, but in case you do:
library(shiny) library(bslib) library(hypertext) # use `hypertext::tags` explicitly to avoid clashing with `shiny::tags`. ht <- hypertext::tags card <- function(title, body) { ht$div( class = "card mt-3", ht$div( class = "card-header", title ), ht$div( class = "card-body", ht$p( class = "card-text", body ) ) ) } content <- ht$div( class = "container py-4", card("First card", "Some quick example text."), card("Second card", "Another body of text.") ) |> render() ui <- page( theme = bs_theme(version = 5L), # hypertext renders an HTML string, so wrap in shiny::HTML() HTML(content) ) server <- function(input, output, session) {} shinyApp(ui, server)