gomb

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 4 Imported by: 0

README

gomb — Go Markup Builder

Go Version Test License

gomb is a small Go library for building HTML programmatically using a fluent, type-safe API. There are no templates, no string concatenation, and no reflection — just regular Go functions and method chaining.

go get github.com/ernlel/gomb

Why gomb?

Pain point with text templates How gomb solves it
Syntax errors only found at runtime Go compiler checks everything
Hard to compose and reuse fragments Components are plain Go functions
Escaping easy to forget T() and A() HTML-escape automatically
Attribute order is non-deterministic in templates gomb sorts attributes alphabetically
No IDE refactoring support Full Go tooling: rename, find-references, …

Quick start

import . "github.com/ernlel/gomb"   // dot-import makes E, If, Map, etc. available unqualified

page := E("html").A("lang", "en").C(
    E("head").C(
        E("meta").A("charset", "UTF-8"),
        E("title").T("My App"),
    ),
    E("body").C(
        E("h1").T("Hello, World!"),
        E("p").T("Welcome to gomb."),
    ),
)

fmt.Println(page.ToString())

Output:

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>
      My App
    </title>
  </head>
  <body>
    <h1>
      Hello, World!
    </h1>
    <p>
      Welcome to gomb.
    </p>
  </body>
</html>

Every short method has a long-form alias. Use whichever reads better in context:

// Short (terse DSL)
page := E("html").A("lang", "en").C(
    E("head").C(E("title").T("My App")),
    E("body").C(E("h1").T("Hello"), E("p").T("Welcome")),
)

// Long (self-documenting)
page := El("html").Attr("lang", "en").Children(
    El("head").Children(El("title").Text("My App")),
    El("body").Children(El("h1").Text("Hello"), El("p").Text("Welcome")),
)

// Named inline (closest to HTML)
page := Html(
    Head(TitleElement(Txt("My App"))),
    Body(H1(Txt("Hello")), P(Txt("Welcome"))),
).Attr("lang", "en")
Short Long
E(tag) El(tag)
.A(k, v) .Attr(k, v)
.T(text) .Text(text)
.C(elems...) .Children(elems...)
Named element constructors

For maximum IDE autocomplete and compile-time safety, the html sub-package provides named constructor functions for every HTML element. Import alongside gomb:

import (
    . "github.com/ernlel/gomb"      // core API
    . "github.com/ernlel/gomb/pkg/html"  // named constructors
)

Use the inline style for compact, HTML-like code — pass attributes and children directly as arguments:

page := Html(
    Head(
        Meta(Attr{Key: "charset", Value: "UTF-8"}),
        TitleElement(Txt("My App")),
    ),
    Body(
        H1(Txt("Hello, World!")),
        P(Txt("Welcome to gomb.")),
    ),
).Attr("lang", "en")

Or use the chained style with explicit .Attr() / .Children() / .Text():

page := Html().Attr("lang", "en").Children(
    Head().Children(
        Meta().Attr("charset", "UTF-8"),
        TitleElement().Text("My App"),
    ),
    Body().Children(
        H1().Text("Hello"),
        P().Text("Welcome to gomb."),
    ),
)

Both produce identical output. Txt(s) is shorthand for E("").T(s) — a text node without a wrapping tag.

The html package is a separate module (github.com/ernlel/gomb/pkg/html) and depends only on gomb. Import it only when you want named constructors; the core package stays minimal.

All 114 standard elements — Anchor() to Wbr(). A handful use an Element suffix to avoid colliding with existing gomb functions:

HTML tag Constructor Reason
<a> Anchor() Collides with .A() / .Attr() setters
<input> InputElement() Common variable name
<script> ScriptElement() Common variable name
<style> StyleElement() Collides with .Style() helper
<title> TitleElement() Common variable name
<data> DataElement() Collides with .Data() helper
<map> MapElement() Collides with Map[T]()
<var> VarElement() Collides with var keyword
<time> TimeElement() Collides with time package

Regenerate with: go run ./cmd/gen-html

Core API

Function / method Description
E(tag) / El(tag) Create an element
.A(key, value) / .Attr(key, value) Set an attribute (HTML-escaped)
.T(text) / .Text(text) Set text content (HTML-escaped)
.C(children...) / .Children(children...) Append child elements
.ToString() Render to an HTML string
.Render(w) Write HTML to an io.Writer
Raw(html) Insert pre-rendered HTML verbatim
Fragment(els...) Tag-less wrapper (no extra element)
None Empty element — renders nothing
If(cond, el) Conditionally include an element
IfElse(cond, a, b) Choose between two elements
When(cond, fn) Lazy conditional — fn only called if cond is true
Map(slice, fn) Transform a slice into []Element
Classes(...names) Space-join class names, skipping empties
.Data(key, value) Shorthand for data-* attributes
.Style(css) Shorthand for the style attribute
.Attrs(pairs...) / .As(pairs...) Apply multiple Attr pairs at once
NS(prefix) Create a namespaced attribute builder
.With(fns...) Apply composable transformer functions
Div(), Span(), … Named constructors in github.com/ernlel/gomb/pkg/html
Txt(text) Tag-less text node — shorthand for E("").T(text)

HTML escaping

T() and A() escape &, <, >, and " automatically — user-supplied data is safe by default.

E("p").T(`<script>alert("xss")</script>`)
// → <p>
//     &lt;script&gt;alert(&#34;xss&#34;)&lt;/script&gt;
//   </p>

<script> and <style> content is never entity-encoded (browsers parse these tags as raw text).

E("script").T(`if (a < b) console.log("ok")`)
// → <script>
//     if (a < b) console.log("ok")
//   </script>

Use Raw() to inject pre-rendered fragments or external HTML:

E("div").C(Raw("<span>already safe HTML</span>"))

Fragments

Fragment(els...) creates a tag-less wrapper — it groups multiple elements together without emitting any surrounding HTML tag. This is useful when a component needs to return several sibling elements, or when you want to insert multiple items into a parent without an extra <div>.

// Without Fragment you'd need a wrapper div:
E("div").C(
    E("dt").T("Go"),
    E("dd").T("A statically typed language."),
)

// Fragment emits the children directly — no wrapper tag:
func definition(term, desc string) gomb.Element {
    return Fragment(
        E("dt").T(term),
        E("dd").T(desc),
    )
}

dl := E("dl").C(
    definition("Go",   "A statically typed language."),
    definition("gomb", "An HTML builder for Go."),
)

Output:

<dl>
  <dt>
    Go
  </dt>
  <dd>
    A statically typed language.
  </dd>
  <dt>
    gomb
  </dt>
  <dd>
    An HTML builder for Go.
  </dd>
</dl>

Another common use: returning a group of table cells or list items from a helper without wrapping them in an extra element:

func navLinks(links []Link) gomb.Element {
    items := Map(links, func(l Link) gomb.Element {
        return E("li").C(E("a").A("href", l.URL).T(l.Label))
    })
    return Fragment(items...)   // no wrapping <ul>/<div>
}

E("ul").C(navLinks(siteLinks))

Named attribute spaces

NS(prefix) creates a function that prepends the given prefix to every attribute key. Combine with .Attrs() to keep htmx, Alpine.js, and custom data-* attributes tidy without repeating the prefix on every call.

htmx
hx := gomb.NS("hx-")
E("button").Attrs(
    hx("get", "/api/users"),
    hx("swap", "outerHTML"),
    hx("target", "#user-list"),
    hx("trigger", "click"),
).T("Reload")

Output:

<button hx-get="/api/users" hx-swap="outerHTML" hx-target="#user-list" hx-trigger="click">
  Reload
</button>
Alpine.js
x := gomb.NS("x-")
E("div").Attrs(
    x("data", `{"open": false}`),
    x("show", "open"),
    x("cloak", ""),
)
Custom data attributes
data := gomb.NS("data-")
E("li").Attrs(
    data("user", "42"),
    data("role", "admin"),
    data("sort", "name"),
)
// → <li data-user="42" data-role="admin" data-sort="name">
Mixing namespaces with plain attributes

Attrs() works alongside .A() and .Data():

hx := gomb.NS("hx-")
E("input").
    A("type", "text").
    A("class", "form-input").
    Data("validate", "email").
    Attrs(
        hx("get", "/validate"),
        hx("trigger", "keyup changed delay:500ms"),
    )

CSS helpers

Classes()

Classes() joins class names into a single string, skipping empty strings. This makes conditional CSS classes ergonomic:

E("button").A("class", Classes(
    "btn",
    IfElse(isPrimary, "btn-primary", "btn-secondary"),
    IfElse(isLarge, "text-lg", "text-sm"),
    IfElse(isDisabled, "opacity-50 cursor-not-allowed", ""),
))
// → <button class="btn btn-primary text-lg opacity-50 cursor-not-allowed">
// or (depending on conditions):
// → <button class="btn btn-secondary text-sm">
Style()
E("div").Style("display: flex; align-items: center; gap: 0.5rem")
// → <div style="display: flex; align-items: center; gap: 0.5rem">

Element transformers

.With() accepts one or more func(Element) Element and applies them in order. Package common attribute patterns as reusable, composable modifiers:

// Transformer factories — functions that return transformers
btn := func(e Element) Element {
    return e.A("type", "submit").A("class", Classes("btn", "rounded"))
}
primaryBtn := func(e Element) Element {
    return e.A("class", Classes("btn", "btn-primary", "rounded"))
}
withTooltip := func(text string) func(Element) Element {
    return func(e Element) Element {
        return e.Data("tooltip", text)
    }
}

// Compose them
E("button").T("Save").With(btn, withTooltip("Save your changes"))
E("button").T("Delete").With(primaryBtn, withTooltip("Permanently delete"))

// Transformers can also modify children:
withIcon := func(e Element) Element {
    return e.C(E("span").A("class", "icon").T("★"))
}
E("a").A("href", "/star").T("Favorite").With(withIcon)

Transformers are just functions — they're testable, composable, and can be defined in packages alongside your components.

Rendering

Render(w) writes directly to any io.Writer, including http.ResponseWriter:

func handler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    E("p").T("Hello").Render(w)
}

Components

Components are Go functions that return Element. Any parameters, loops, and conditions are just Go:

func Button(label string, primary bool) Element {
    return E("button").
        A("class", Classes("btn", IfElse(primary, "btn-primary", ""))).
        A("type", "submit").
        T(label)
}

func NavBar(links []NavLink) Element {
    items := Map(links, func(l NavLink) Element {
        return E("li").C(E("a").A("href", l.URL).T(l.Label))
    })
    return E("nav").C(E("ul").C(items...))
}

Layouts

Wrap content in a layout function:

func Layout(title string, body gomb.Element) gomb.Element {
    return E("html").A("lang", "en").C(
        E("head").C(
            E("meta").A("charset", "UTF-8"),
            E("title").T(title),
            E("link").A("rel", "stylesheet").A("href", "/static/app.css"),
        ),
        E("body").C(Header(), body, Footer()),
    )
}

func IndexPage() gomb.Element {
    return Layout("Home", E("main").C(
        E("h1").T("Welcome"),
        E("p").T("This is the home page."),
    ))
}

Conditionals and loops

// If — render an element only when a condition is true
E("ul").C(
    If(user.IsAdmin, E("li").C(E("a").A("href", "/admin").T("Admin"))),
    E("li").C(E("a").A("href", "/profile").T("Profile")),
)

// IfElse — choose between two elements
IfElse(user.LoggedIn,
    E("a").A("href", "/logout").T("Log out"),
    E("a").A("href", "/login").T("Log in"),
)

// Go if/else inside a component function
func greeting(user *User) gomb.Element {
    if user == nil {
        return E("p").T("Hello, guest!")
    }
    return E("p").T("Hello, " + user.Name + "!")
}

// Map — render a list from a slice
names := []string{"Alice", "Bob", "Carol"}
E("ul").C(Map(names, func(name string) gomb.Element {
    return E("li").T(name)
})...)

// When — lazy conditional: fn is only called if the condition is true
// Use When instead of If when the element is expensive or has side-effects
E("ul").C(
    When(user != nil, func() Element {
        // This closure only runs when user != nil
        return E("li").C(E("a").A("href", "/profile").T(user.Name))
    }),
    E("li").C(E("a").A("href", "/home").T("Home")),
)

Tailwind CSS

Tailwind classes are regular strings — use Classes() for terse conditional composition:

E("button").A("class", Classes(
    "bg-blue-600 hover:bg-blue-700",
    "text-white font-bold",
    "py-2 px-4 rounded",
    IfElse(disabled, "opacity-50 cursor-not-allowed", ""),
)).T("Click me")

For Tailwind CDN (development / demos):

E("link").
    A("rel", "stylesheet").
    A("href", "https://cdn.jsdelivr.net/npm/tailwindcss@3/dist/tailwind.min.css")

htmx integration

htmx directives are HTML attributes — use .A() directly, or define a namespace with NS() for less repetition:

// Vanilla A() calls
E("button").
    A("hx-get", "/api/users").
    A("hx-target", "#user-list").
    A("hx-swap", "innerHTML").
    T("Reload users")

// Or with NS() — no repeating "hx-"
hx := NS("hx-")
E("button").Attrs(
    hx("get", "/api/users"),
    hx("target", "#user-list"),
    hx("swap", "innerHTML"),
).T("Reload users")

// An inline-edit form using namespace
E("form").Attrs(
    hx("put", fmt.Sprintf("/users/%d", user.ID)),
    hx("target", fmt.Sprintf("#user-%d", user.ID)),
    hx("swap", "outerHTML"),
).C(
    E("input").A("type", "text").A("name", "name").A("value", user.Name),
    E("button").A("type", "submit").T("Save"),
)

See examples/htmx/ for a full task-list app.

Alpine.js integration

Alpine.js x-data, x-show, @click, :class, and other directives are just HTML attributes. JSON in x-data is HTML-escaped automatically and the browser decodes it back correctly.

x := NS("x-")

// Counter
E("div").Attrs(x("data", `{"count": 0}`)).C(
    E("button").A("@click", "count--").T("-"),
    E("span").A("x-text", "count"),
    E("button").A("@click", "count++").T("+"),
)

// Toggle visibility
E("div").Attrs(
    x("data", `{"open": false}`),
    x("show", "open"),
    x("cloak", ""),
).C(
    E("button").A("@click", "open = !open").T("Toggle"),
    E("p").A("x-show", "open").T("Visible when open is true"),
)

See examples/alpinejs/ for accordion, tabs, modal, and live search examples.

Caching

Because Element is an immutable value type, rendered HTML strings can be stored and replayed freely.

Static component (sync.Once)

var (
    navOnce sync.Once
    navHTML string
)

func Nav() string {
    navOnce.Do(func() { navHTML = buildNav().ToString() })
    return navHTML
}

Per-key TTL cache

var cache sync.Map

type entry struct { html string; exp time.Time }

func Cached(key string, ttl time.Duration, build func() gomb.Element) string {
    if v, ok := cache.Load(key); ok {
        if e := v.(entry); time.Now().Before(e.exp) {
            return e.html
        }
    }
    html := build().ToString()
    cache.Store(key, entry{html, time.Now().Add(ttl)})
    return html
}

See examples/caching/ for a complete demo including an HTTP response-level cache middleware.

Converting existing HTML

The pkg/markup_to_gomb package converts an HTML string into the equivalent gomb Go code. Useful for migrating existing templates or pasting in HTML from a design tool.

import "github.com/ernlel/gomb/pkg/markup_to_gomb"

code, err := markup_to_gomb.GenerateGombFromMarkup(`
    <div class="card">
        <h2>Title</h2>
        <p>Body text.</p>
    </div>
`)
fmt.Println(code)

See examples/markup_to_gomb/ for a file-based conversion tool.

Examples

Example What it shows
examples/components/ Reusable component functions served over HTTP
examples/layout/ Multi-page site with a shared layout, header, and footer (Tailwind)
examples/htmx/ Dynamic task list with htmx partial updates
examples/alpinejs/ Client-side interactivity (counter, accordion, tabs, modal, live search)
examples/caching/ Component cache, per-key TTL cache, and response-level cache middleware
examples/test1/ Full landing page generated from gomb (Tailwind)
examples/html-elements/ Named constructors for all 114 HTML elements — inline and chained styles
examples/markup_to_gomb/ Convert HTML markup to gomb code

Self-closing tags

The following void elements are rendered without a closing tag automatically: area, base, br, col, embed, hr, img, input, link, meta, param, source, track, wbr.

E("img").A("src", "logo.png").A("alt", "Logo")
// → <img alt="Logo" src="logo.png" />

Boolean attributes

Pass an empty string value to render a boolean attribute without a value:

E("input").A("type", "checkbox").A("checked", "").A("disabled", "")
// → <input checked disabled type="checkbox" />

Introspection

The Element struct fields are exported so you can inspect or walk the tree in custom tooling, tests, or middleware:

el := E("div").A("class", "card").C(
    E("h2").T("Title"),
    E("p").T("Body"),
)

// Inspect
tag := el.Tag                 // "div"
cls := el.Attributes["class"] // "card"
txt := el.ChildNodes[0].TextContent  // "Title"

// Walk the tree
var walk func(e Element)
walk = func(e Element) {
    fmt.Println(e.Tag)
    for _, c := range e.ChildNodes {
        walk(c)
    }
}
walk(el)

The mutable builder methods (A, C, T, etc.) always return a copy — inspecting a rendered element won't affect anything downstream.

Code style guide

Short or long API — choose by audience

The short form (E, A, T, C) reads like a DSL and is ideal for page-level composition where you're writing lots of markup:

page := E("html").A("lang", "en").C(
    E("head").C(E("title").T("Home")),
    E("body").C(E("h1").T("Welcome")),
)

The long form (El, Attr, Text, Children) is self-documenting — use it in public packages, shared components, and code that non-Go developers might read:

func SiteLayout(title string, body Element) Element {
    return El("html").Attr("lang", "en").Children(
        El("head").Children(El("title").Text(title)),
        El("body").Children(Header(), body, Footer()),
    )
}
Chain vertically for clarity

One method call per line — attributes grouped, children indented:

// Good
E("button").
    A("type", "submit").
    A("class", Classes("btn", "btn-primary")).
    Data("action", "save").
    T("Save")

// Hard to scan
E("button").A("type","submit").A("class",Classes("btn","btn-primary")).Data("action","save").T("Save")
Components are functions

Extract anything used more than once into a function. Return Element, accept any parameters you need:

func Card(title, body string) Element {
    return E("div").A("class", "card").C(
        E("h3").A("class", "card-title").T(title),
        E("p").A("class", "card-body").T(body),
    )
}

func CardList(items []CardData) Element {
    return E("div").A("class", "card-list").C(
        Map(items, func(d CardData) Element {
            return Card(d.Title, d.Body)
        })...,
    )
}
Reuse attribute bundles with NS()

When a prefix repeats across many elements, define a namespace at the package or function level:

var hx = gomb.NS("hx-")

func liveSearch() Element {
    return E("input").As(
        hx("get", "/search"),
        hx("trigger", "keyup changed delay:300ms"),
        hx("target", "#results"),
        hx("swap", "innerHTML"),
    ).A("type", "search").A("placeholder", "Search...")
}
Parameterize transformers

func(Element) Element is the signature — wrap in a closure to accept configuration:

func sizedText(size string) func(Element) Element {
    return func(e Element) Element {
        return e.A("class", Classes("text-"+size))
    }
}

E("p").T("Title").With(sizedText("xl"))
E("p").T("Body").With(sizedText("base"))
Conditionals — pick the right tool
Pattern When
Go if/else inside a component The condition drives logic, not just presence
If(cond, el) Inline inside a .C() call, element already built
When(cond, fn) The element is expensive or has side-effects
IfElse(cond, a, b) Swapping between two inline elements
Classes("base", IfElse(cond, "on", "off")) Conditional CSS classes
// Go if/else — multiple branches or complex logic
func StatusBadge(status string) Element {
    var color string
    switch status {
    case "active":   color = "bg-green-500"
    case "paused":   color = "bg-yellow-500"
    default:         color = "bg-gray-500"
    }
    return E("span").A("class", Classes("badge", color)).T(status)
}

// If — simple inline presence
E("ul").C(
    If(isAdmin, E("li").C(E("a").A("href", "/admin").T("Admin"))),
    E("li").C(E("a").A("href", "/home").T("Home")),
)

// Classes + IfElse — conditional CSS
E("button").A("class", Classes(
    "btn",
    IfElse(active, "btn-active", "btn-inactive"),
    IfElse(large, "text-lg", ""),
))
Layouts: wrap, don't repeat

A layout function accepts title and body, returns the full page:

func Layout(title string, body Element) Element {
    return E("html").A("lang", "en").C(
        E("head").C(
            E("meta").A("charset", "UTF-8"),
            E("title").T(title),
        ),
        E("body").C(Header(), body, Footer()),
    )
}

Every page becomes one line:

func HomePage() Element     { return Layout("Home", homeContent()) }
func AboutPage() Element    { return Layout("About", aboutContent()) }

License

MIT — see LICENSE. Contributions welcome — see CONTRIBUTING.md.

Documentation

Overview

Package gomb provides a fluent, type-safe API for building HTML programmatically in Go. Elements are immutable value types: every method returns a new Element, making them safe to share and reuse across goroutines without synchronisation.

Quick start:

import . "github.com/ernlel/gomb"

page := E("html").A("lang", "en").C(
    E("head").C(E("title").T("My App")),
    E("body").C(
        E("h1").T("Hello, World!"),
        E("p").T("Welcome to gomb."),
    ),
)
fmt.Println(page.ToString())

Index

Constants

This section is empty.

Variables

View Source
var None = Element{}

None is the zero Element. It renders to nothing and is the canonical return value when a conditional helper has no output.

Functions

func Classes

func Classes(names ...string) string

Classes returns a space-joined string from the given class names, skipping empty strings. This makes conditional CSS classes ergonomic:

E("div").A("class", Classes("base", IfElse(active, "active", "")))

func NS

func NS(prefix string) func(key, value string) Attr

NS returns a function that builds namespaced attribute pairs. The returned function prepends the given prefix to every key, enabling domain-specific attribute shortcuts without repeating the prefix.

// htmx attributes
hx := gomb.NS("hx-")
E("button").Attrs(
    hx("swap", "outerHTML"),
    hx("target", "#result"),
    hx("get", "/api/data"),
)

// Alpine.js x-data / x-show / x-text etc.
x := gomb.NS("x-")
E("div").Attrs(x("data", `{"open": false}`), x("show", "open"))

// Custom data attributes
data := gomb.NS("data-")
E("li").Attrs(data("user", "42"), data("role", "admin"))

Types

type Attr

type Attr struct {
	Key, Value string
}

Attr is a key-value attribute pair.

type Element

type Element struct {
	Tag         string
	Attributes  map[string]string
	ChildNodes  []Element
	TextContent string
	// contains filtered or unexported fields
}

Element represents an HTML element. It is an immutable value type; all mutating methods return a new Element rather than modifying the receiver.

Fields are exported for introspection and custom tooling (e.g. walking the tree, inspecting state). For building elements prefer the fluent API: E, A/T/C, etc.

func E

func E(tag string) Element

E creates a new HTML element with the given tag name. Use an empty tag ("") to create a tag-less fragment.

func El

func El(tag string) Element

El is an alias for E, for users who prefer a slightly longer name. Identical to E in every way.

func Fragment

func Fragment(elements ...Element) Element

Fragment creates a tag-less wrapper around multiple elements. The fragment itself produces no markup; only its children are rendered.

E("ul").C(Fragment(
    E("li").T("one"),
    E("li").T("two"),
))

func If

func If(cond bool, el Element) Element

If returns el when cond is true, otherwise returns None.

E("ul").C(
    If(isLoggedIn, E("li").C(E("a").A("href", "/logout").T("Logout"))),
    E("li").C(E("a").A("href", "/home").T("Home")),
)

func IfElse

func IfElse(cond bool, ifEl, elseEl Element) Element

IfElse returns ifEl when cond is true, otherwise returns elseEl.

IfElse(isLoggedIn,
    E("a").A("href", "/logout").T("Logout"),
    E("a").A("href", "/login").T("Login"),
)

func Map

func Map[T any](items []T, fn func(T) Element) []Element

Map transforms a slice of values into a slice of Elements using fn. Use the spread operator to pass the result to C():

names := []string{"Alice", "Bob", "Carol"}
E("ul").C(Map(names, func(name string) Element {
    return E("li").T(name)
})...)

func Raw

func Raw(rawHTML string) Element

Raw creates an element whose content is written verbatim to the output without any HTML escaping. Use it for pre-rendered HTML fragments or for content inside <script> and <style> tags that must not be entity-encoded.

E("script").A("type", "text/javascript").C(Raw(`console.log("hello")`))
E("style").C(Raw(`.btn { color: red; }`))
E("div").C(Raw("<span>pre-rendered</span>"))

func Txt

func Txt(content string) Element

Txt creates a tag-less text element. It is equivalent to E("").T(text) — a text node without a wrapping tag. Useful with inline constructors from the html package:

Div(gomb.Attr{Key: "class", Value: "card"}, H2(Txt("Title")))

func When

func When(cond bool, fn func() Element) Element

When returns el derived from fn() when cond is true, otherwise returns None. Unlike If, the element is not constructed unless the condition is true.

E("ul").C(
    When(user != nil, func() Element {
        return E("li").T("Hello, " + user.Name)
    }),
)

func (Element) A

func (e Element) A(key, value string) Element

A returns a new Element with the given attribute set (or overwritten). The attribute value is HTML-escaped when the element is rendered, so it is safe to pass raw strings including characters like <, >, &, and ".

Boolean attributes (e.g. "disabled", "checked") can be set by passing an empty value:

E("input").A("type", "checkbox").A("checked", "")

func (Element) As

func (e Element) As(attrs ...Attr) Element

As is an alias for Attrs — the short plural form for applying multiple attribute pairs at once.

hx := gomb.NS("hx-")
E("button").As(hx("get", "/api"), hx("swap", "outerHTML")).T("Load")

func (Element) Attr

func (e Element) Attr(key, value string) Element

Attr is an alias for A — identical behaviour, longer name for readability.

func (Element) Attrs

func (e Element) Attrs(attrs ...Attr) Element

Attrs applies multiple Attr pairs to the element in one call.

E("div").Attrs(
    Attr{"class", "container"},
    Attr{"id", "main"},
)

func (Element) C

func (e Element) C(elements ...Element) Element

C returns a new Element with the given elements appended to its children. Passing None or a zero Element has no visible effect on the rendered output.

To append a slice of elements use the spread operator:

items := Map(list, func(s string) Element { return E("li").T(s) })
E("ul").C(items...)

func (Element) Children

func (e Element) Children(elements ...Element) Element

Children is an alias for C — identical behaviour, longer name for readability.

func (Element) Data

func (e Element) Data(key, value string) Element

Data returns a "data-*" attribute builder for data attributes.

E("div").Data("count", "0").Data("user", "42")
// <div data-count="0" data-user="42">

func (Element) Render

func (e Element) Render(w io.Writer) error

Render writes the HTML representation of the element to w. It is a convenience wrapper around ToString for use with http.ResponseWriter and other io.Writer targets.

func handler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    page.Render(w)
}

func (Element) Style

func (e Element) Style(css string) Element

Style sets the "style" attribute on the element. For multiple CSS declarations pass a semicolon-separated string:

E("div").Style("color: red; font-weight: bold")

func (Element) T

func (e Element) T(value string) Element

T returns a new Element with the given text content. The text is HTML-escaped when the element is rendered, preventing XSS when displaying user-supplied data. For unescaped output use Raw().

func (Element) Text

func (e Element) Text(value string) Element

Text is an alias for T — identical behaviour, longer name for readability.

func (Element) ToString

func (e Element) ToString() string

ToString renders the element tree to an indented HTML string.

func (Element) With

func (e Element) With(fns ...func(Element) Element) Element

With applies the given transformer functions to the element and returns the result. Transformer functions receive and return an Element, allowing custom logic to be packaged as reusable modifiers.

// A transformer that adds common attributes
btn := func(e gomb.Element) gomb.Element {
    return e.A("type", "submit").A("class", "btn")
}
// A transformer that conditionally adds children
withHelp := func(e gomb.Element) gomb.Element {
    return e.C(E("span").T("?"))
}
E("form").C(
    E("button").T("Save").With(btn),
    E("input").With(btn, withHelp),
)

Directories

Path Synopsis
examples
alpinejs command
Package main demonstrates using gomb with Alpine.js for client-side interactivity.
Package main demonstrates using gomb with Alpine.js for client-side interactivity.
caching command
Package main demonstrates several caching strategies for gomb-rendered HTML.
Package main demonstrates several caching strategies for gomb-rendered HTML.
htmx command
Package main demonstrates using gomb with htmx for server-driven, dynamic UIs.
Package main demonstrates using gomb with htmx for server-driven, dynamic UIs.
test1 command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL