hypertext

R-CMD-check CRAN status Lifecycle: stable

html element construction for R.

hypertext provides a deterministic, framework-agnostic DSL for building html nodes and rendering them to a string.

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:

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)