yawaramin/dream-html
{ "createdAt": "2023-04-22T00:47:10Z", "defaultBranch": "main", "description": "Type-safe markup rendering, form validation, and routing for OCaml Dream web framework", "fullName": "yawaramin/dream-html", "homepage": "https://yawaramin.github.io/dream-html/", "language": "OCaml", "name": "dream-html", "pushedAt": "2025-11-23T19:05:13Z", "stargazersCount": 197, "topics": [ "html", "htmx", "mathml", "ocaml", "svg" ], "updatedAt": "2025-11-23T22:17:38Z", "url": "https://github.com/yawaramin/dream-html"}dream-html - build robust and maintainable OCaml Dream webapps
Section titled “dream-html - build robust and maintainable OCaml Dream webapps”Copyright 2023 Yawar Amin
This file is part of dream-html.
dream-html is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
dream-html is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with dream-html. If not, see https://www.gnu.org/licenses/.
This project started as a simple HTML library; it has evolved into something more over time. Here are the highlights:
- Closely integrated with the Dream web framework for OCaml
- Generate HTML using type-safe functions and values
- MathML and SVG support
- Support for htmx attributes
- Type-safe HTML form and query decoding
- Type-safe path parameter parsing and printing
[!NOTE] If you’re not using Dream, you can still use the HTML/SVG/MathML/htmx generation features using the
pure-htmlpackage.
First look
Section titled “First look”let%path greeting = "/%s"
let hello _request who = let open Dream_html in let open HTML in respond ( html [lang "en"] [ head [] [ title [] "dream-html first look"; ]; body [] [ h1 [] [txt "Hello, %s!" who]; p [] [ txt "This page is at: "; a [path_attr href greeting who] [txt "this URL"]; txt "."; ]; ]; ] )
let () = Dream.run @@ Dream.logger @@ Dream.router [ Dream_html.get greeting hello; ]Rendered HTML:
<!DOCTYPE html><html lang="en"> <head> <title>dream-html first look</title> </head> <body> <h1>Hello, me!</h1> <p>This page is at: <a href="/me">this URL</a>.</p> </body></html>Security (HTML escaping)
Section titled “Security (HTML escaping)”Attribute and text values are escaped using rules very similar to standards- compliant web browsers:
utop # open Dream_html;;utop # open HTML;;
utop # let user_input = "<script>alert('You have been pwned')</script>";;val user_input : string = "<script>alert('You have been pwned')</script>"
utop # p [] [txt "%s" user_input];;- : node = <p><script>alert('You have been pwned')</script></p>
utop # div [title_ {|"%s|} user_input] [];;- : node = <div title=""<script>alert('You have been pwned')</script>"></div>How to install
Section titled “How to install”Make sure your local copy of the opam repository is up-to-date first:
opam updateopam install dream-html # or pure-html if you don't want the Dream integrationAlternatively, to install the latest commit that may not have been released yet, you have two options. If you need only the HTML generation:
opam pin add pure-html git+https://github.com/yawaramin/dream-htmlIf you also need the Dream integration:
opam pin add pure-html git+https://github.com/yawaramin/dream-htmlopam pin add dream-html git+https://github.com/yawaramin/dream-htmlRespond with an HTML node from a handler:
Dream_html.respond greetingCompose multiple HTML nodes together into a single node without an extra DOM node, like React fragments:
let view = null [p [] [txt "Hello"]; p [] [txt "World"]]String interpolation of text nodes using txt and any attribute which takes a
string value:
let greet name = p [id "greet-%s" name] [txt "Hello, %s!" name]Conditionally render an attribute, and statically enforce void elements as childless:
let entry = input [ if should_focus then autofocus else null_; id "email"; name "email"; value "Email address"; ]Embed HTML comments in the generated document:
div [] [comment "TODO: xyz."; p [] [txt "Hello!"]](* <div><!-- TODO: xyz. -->Hello!</div> *)Conveniently hot-reload the webapp in the browser using the
Dream_html.Livereload module. See the API reference for details.
Form validation
Section titled “Form validation”There is also a module with helpers for request form and query validation; see
Dream_html.Form
for details. See also the convenience helpers Dream_html.form and
Dream_html.query.
Type-safe path parameter parsing and printing
Section titled “Type-safe path parameter parsing and printing”Type-safe wrappers for Dream routing functionality are provided; details are
shown in the
Dream_html page.
See also the PPX documentation for setup and usage instructions.
Import HTML
Section titled “Import HTML”One issue that you may come across is that the syntax of HTML is different from the syntax of dream-html markup. To ease this problem, you may use the translation webapp in the landing page.
Note that the dream-html code is not formatted nicely, because the expectation is that you will use ocamlformat to fix the formatting.
Also note that the translation done by this bookmarklet is on a best-effort basis. Many web pages don’t strictly conform to the rules of correct HTML markup, so you will likely need to fix those issues for your build to work.
Run the test and print out diff if it fails:
dune test # Will also exit 1 on failureSet the new version of the output as correct:
dune promotePrior art/design notes
Section titled “Prior art/design notes”Surface design obviously lifted straight from elm-html.
Implementation inspired by both elm-html and ScalaTags.
Many languages and libraries have similar HTML embedded DSLs:
- Phlex - Ruby
- Arbre - Ruby
- hiccl - Common Lisp
- scribble-html-lib - Racket
- hiccup - Clojure
- std/htmlgen - Nim
- Falco.Markup - F#
- htpy - Python
- HTML::Tiny - Perl
- j2html - Java
- Lucid - Haskell