gml is a lightweight, composable DSL for building HTML interfaces directly in Go. It allows you to describe HTML structure using idiomatic Go code while preserving clarity, type safety, and explicit control over rendering.
The library is designed to be minimal, predictable, and runtime free.
import "github.com/501urchin/gml"
func Example() gml.GmlElement {
return gml.Div().
Attributes(
gml.Class("flex flex-col"),
gml.Id("demo"),
gml.Attribute("aria-hidden", "false"),
).
Children(
gml.P().Children(
gml.Content("hello from"),
),
gml.Raw("<p>GML !!!</p>"),
)
}<div class="flex flex-col" id="demo" aria-hidden="false">
<p>hello from</p>
<p>GML !!!</p>
</div>At the core of gml is the GmlElement interface. Every element in the library such as gml.Div(), gml.P(), or gml.Raw() implements this interface.
type GmlElement interface {
// Attributes returns the receiver with the provided attributes appended.
Attributes(attributes ...Attr) GmlElement
// Children returns the receiver with the provided child elements appended.
Children(children ...GmlElement) GmlElement
// Render writes the rendered HTML representation of the element to w.
// If rendering this element or any of its children returns an error,
// rendering stops immediately and that error is returned.
Render(ctx context.Context, w io.Writer) error
// RenderBestEffort writes the rendered HTML representation of the element to w.
// Unlike Render, this method continues rendering remaining children even if
// one or more children return an error. The returned error, if non-nil,
// represents at least one failure during rendering.
RenderBestEffort(ctx context.Context, w io.Writer) error
// RenderHtml renders the element and returns the resulting HTML as a byte slice.
// This is intended for use cases that require raw HTML output rather than
// streaming to an io.Writer.
RenderHtml(ctx context.Context) ([]byte, error)
}- Each HTML construct is represented as a Go value implementing
GmlElement. - Elements are composed by calling
Attributes(...)andChildren(...). - Rendering is explicit and opt-in via one of the
Rendermethods.
gml has no runtime or virtual DOM. Rendering is performed via straightforward recursion:
- Each element knows how to render itself.
- Child elements are rendered by calling their
Rendermethod. - The full HTML output is produced by recursively walking the element tree.
Because all elements implement the same interface, you can define custom elements by implementing GmlElement yourself and seamlessly integrate them into existing trees.
- Explicit rendering: you control when and how HTML is produced
- Composable primitives: build higher-level components from simple elements
- Idiomatic Go: no templates, no DSL parser, just Go
gml implements most standard HTML elements (for example div, p, span, etc.). Each element is created by calling its corresponding constructor function.
To render a div, you first create the element:
gml.Div()This call returns an internal concrete type that implements the GmlElement interface. At this stage, the element has no attributes and no children it is just a value representing a div node.
Attributes are added by calling the Attributes(...) method. Each attribute is represented by the Attr type. gml's internal type appends the attribute to the attributes array and returns the receiver
gml.Div().Attributes(
gml.Attribute("class", "myclass"),
)The gml.Attribute function accepts a key and a value and returns an Attr.
The internal element type appends the provided attributes to its attribute slice and returns the receiver.
Repeated calls to .Attributes() add more attributes to the existing list rather than replacing them.
gml.Div().Attributes(
gml.Attribute("class", "myclass"),
).Attributes(
gml.Id("demo"),
)The resulting div has both class="myclass" and id="demo".
Note: Attribute values are not escaped automatically. If you are inserting untrusted or structured data, you must escape it yourself.
gml.Div().Attributes(
gml.Attribute("data-tag", html.EscapeString(jsonString)),
)Because repeatedly calling gml.Attribute(key, value) can be verbose, gml provides convenience helpers for commonly used attributes.
gml.Div().Attributes(
gml.Class("myclass"),
)These helpers return the same Attr type, but with clearer intent and less boilerplate.
Here’s a rewritten version for children instead of attributes, keeping the same style and structure:
Child elements are added using the Children(...) method. This method accepts any value that implements the GmlElement interface.
gml.Div().Children(
gml.Content("child1"),
gml.P(),
)The internal element type appends the provided children to its children slice and returns the receiver.
Repeated calls to .Children() add more children to the existing list rather than replacing them.
gml.Div().Children(
gml.Content("child1"),
).Children(
gml.Content("child2"),
)The resulting <div> contains both "child1" and "child2" as children.
gml.Content is a built-in element that implements GmlElement and renders arbitrary values as text. It accepts a value of type any and attempts to convert it to a string representation.
Examples:
gml.Content("child") // renders: "child"
gml.Content(1) // renders: "1"
gml.Content(false) // renders: "false"
gml.Content(nil) // renders: "nil"This makes it convenient to inject dynamic values without manually converting them beforehand.
Note: Content values are not escaped automatically. If you are inserting untrusted or structured data, you must escape it yourself.
Sometimes, we want to render raw HTML directly.
gml provides a gml.Raw() method for this purpose. It accepts a string containing the HTML you want to render.
gml.Raw("<p>hello</p>")gmlx is an extension package built on top of gml that provides higher-level helpers and custom elements for common control-flow and composition patterns.
Key features include:
gmlx.If– conditional renderinggmlx.Group– grouping multiple elements into a singleGmlElementgmlx.Map– iterating over collections to produce element trees
gmlx.If(LoggedIn, func() gml.GmlElement {
return gml.Content("LoggedIn")
})var items []customType
gmlx.Map(items, func(index int, item customType) gml.GmlElement {
return gml.Div().Children(
gml.Content(item.Field1),
gml.Content(item.Field2),
)
})gmlx.Group(
gml.DocTypeHtml(),
gml.Html(),
)