html_dsl

Hex Docs test

A fun, modular, and simple way to create HTML in Gleam.

Premise

I wanted a simple framework to build HTML and I saw a way forward with strings. Think of this as building a one way buffer to generate HTML. I wanted to avoid using a lot of intermediate steps like using a list of tuples or a list of records. I wanted to get straight to the point and build the HTML string as I go along. This is a very simple way to build HTML and I wanted to keep it that way.

I thought that Gleam had a very unique type system in which I could wrap strings around a named type like Html or Head. The way I implemented it does restrict general reactivity through Gleam itself, but I never thought making this a standalone framework as an option. The idea was to combine this with existing frontend libraries like HTMX to add that touch of reactivity. The backend/DSL would choose what to generate based on requests and headers for a more dynamic experience.

Going forward

I’m refining the way the elements are built as well as adding more elements to the DSL. I add deeper documentation based one what I think could cause problems. If you find something difficult, feel free to reach out!

Installation

gleam add html_dsl

Examples

import gleam/io
import html_dsl/types/html.{body, button, h1, header, html, nav}
import html_dsl/types/html/head.{charset, head, meta, script, title}
import html_dsl/types/html/lists.{ul}
import html_dsl/types/html/form.{Submit, Text, element, form, input, label}
import html_dsl/types/attribute.{class, id}
import html_dsl/types/html/form/select.{select}
import gleam/option.{None}

/// Example of creating an HTML string
pub fn main() {
 html(
   lang: "en",
   head: head()
     |> title("Hello, Gleam!")
     |> charset("UTF-8")
     |> meta("viewport", "width=device-width, initial-scale=1.0")
     |> meta("description", "A Gleam program that generates HTML.")
     |> script("https://cdn.tailwindcss.com")
     |> head.end(),
   body: body(
     attributes: id(None, "main-content")
       |> class("bg-black text-white"),
     inner: header(
         class(None, "grid place-content-center"),
         nav(None, "Hello"),
       )
       <> case False {
         True -> h1(None, "True")
         False -> h1(None, "False")
       }
       <> ul(None)
       |> lists.add(
         attribute.new()
           |> class("text-blue-400"),
         "This is a list item",
       )
       |> lists.add(None, "This is another list item")
       |> lists.end()
       <> form(
         class(None, "flex")
         |> attribute.add("method", "GET")
         |> attribute.add("action", "#"),
       )
       |> label(None, "Name:")
       |> input(None, Text, "name")
       |> input(None, Submit, "submit")
       |> element(button(None, "Click me!"))
       |> select(None)
       |> select.add(
         attribute.add(None, "selected", "selected")
           |> attribute.add("disabled", "disabled"),
         "",
         "--Please choose an option--",
       )
       |> select.add(None, value: "something", label: "Something")
       |> select.add(None, value: "else", label: "Else")
       |> select.end()
       |> form.end(),
   ),
 )
 |> html.force()
 |> html.html_to_string()
 |> io.println()
}

So why use strings to build each element? Ultimately everything is parsed into a string so I wanted to get straight to the point with little to no intermediate step. Don’t get me wrong, I still have intermediate’s here and there, but that’s more for explicitness. It prevents bad HTML from being written.

Development

This is for use for contributors to the project.

./dev.sh run   # Run the project
./dev.sh test  # Run the tests
./dev.sh docs  # Generate the documentation
./dev.sh build # Build the project
Search Document