Skip to content
Back to skills

Element Html Builder

ASecurity

Element is a zero dependency library to efficiently generate HTML, without templates in Go

  • 666 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added February 8, 2026
developmentgobashgitapidocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill element-html-builder --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Element Html Builder?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Element Html Builder
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-element-html-builder/badge)](https://www.skillsdirectory.com/skills/majiayu000-element-html-builder)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: element-html-builder
description: Element is a zero dependency library to efficiently generate HTML, without templates in Go
---

# Element - AI Agent Documentation

> Programmatic HTML generation in Go without templates.

## Overview

Element is a zero-dependency Go library that generates HTML programmatically by leveraging Go's natural function execution order. Instead of templates, you write Go code that mirrors HTML's tree structure.

## Benefits

1. You never leave the safety, speed, and familiarity of Go
    - Compiles single-pass with the rest of your Go program -- no extra annotations or build steps
    - All of Go is available at any point in your code
2. Zero dependencies
3. Buffer pools for super-high traffic situations
4. Go's formatting naturally follows the HTML tree structure

## Installation

```bash
go get -u github.com/rohanthewiz/element
```

## General Strategy

1. Write the opening tag with attributes
2. Render the children and closing tag

## Core Concepts

### The Builder Pattern

All HTML generation flows through a `Builder` instance. The builder maintains an internal buffer where tags are written immediately upon method calls.

### Immediate Tag Writing

**Key Insight:** When you call a builder method like `b.Div()`, the opening tag `<div>` is written **immediately** to an internal buffer. The returned element must be **terminated** with `R()`, `T()`, or `F()` to write the closing tag.

```go
b := element.NewBuilder()
b.Div("id", "container")  // "<div id="container">" is NOW in the buffer
// The element is returned, waiting for termination
```

Go's function argument evaluation order means children are processed (and their tags written) before the parent's `R()` closes the parent tag. This is what makes nested structures work correctly.

### Terminate all tags

Every non-self-closing element **must** be terminated with `R()`, `T()`, or `F()`. Self-closing elements like `Br`, `Img`, `Input`, `Hr`, and `Meta` don't require termination, but to keep things consistent, close these tags with a sole `R()` or `T()`.

## Basic Usage

### Creating a Builder

```go
import "github.com/rohanthewiz/element"

// Standard creation
b := element.NewBuilder()

// Shorthand (equivalent)
b := element.B()

// From pool (for high-throughput HTTP handlers)
b := element.AcquireBuilder()
defer element.ReleaseBuilder(b)
```

### Simple Elements

```go
b := element.B()

// Element with text content
b.P().T("Hello, World!")
// Output: <p>Hello, World!</p>

// Element with children
b.Div().R(
    b.P().T("First paragraph"),
    b.P().T("Second paragraph"),
)
// Output: <div><p>First paragraph</p><p>Second paragraph</p></div>

// Empty element
b.Div().R()
// Output: <div></div>

// Get the HTML string
html := b.String()
```

### Complete Page Example

```go
b := element.B()
b.Html().R(
    b.Head().R(
        b.Title().T("My Page"),
        b.Meta("charset", "utf-8"),
    ),
    b.Body().R(
        b.DivClass("container").R(
            b.H1().T("Welcome"),
            b.P().T("Hello from Element!"),
        ),
    ),
)
html := b.String()
```

### Building with Multiple Functions

```go
func main() {
	b := element.B()

	b.DivClass("container").R(
		b.H2().T("Section 1"),
		generateList(b),
	)

	fmt.Println(b.Pretty())
	/* // Output:
	<div class="container">
	  <h2>Section 1</h2>
	  <ul>
	    <li>Item 1</li>
	    <li>Item 2</li>
	  </ul>
	</div>
	 */
}

func generateList(b *element.Builder) (x any) { // we don't care about the return
	// Everything is rendered in the builder immediately!
	b.Ul().R(
		b.Li().T("Item 1"),
		b.Li().T("Item 2"),
	)
	return
}
```

## API Reference

### Builder Creation

| Function                    | Description                             |
| --------------------------- | --------------------------------------- |
| `element.NewBuilder()`      | Create a new builder                    |
| `element.B()`               | Shorthand for NewBuilder()              |
| `element.AcquireBuilder()`  | Get builder from pool (high-throughput) |
| `element.ReleaseBuilder(b)` | Return builder to pool                  |

### Termination Methods

| Method               | Use Case                           | Example                    |
| -------------------- | ---------------------------------- | -------------------------- |
| `R(children...)`     | Elements with children or empty    | `b.Div().R(b.P().T("hi"))` |
| `T(strings...)`      | Text-only content (most efficient) | `b.P().T("Hello")`         |
| `F(format, args...)` | Formatted text (like fmt.Sprintf)  | `b.P().F("Count: %d", 42)` |

### Element Methods

All standard HTML elements are available: `Div`, `Span`, `P`, `A`, `H1`-`H6`, `Ul`, `Ol`, `Li`, `Table`, `Tr`, `Td`, `Th`, `Form`, `Input`, `Button`, `Label`, `Img`, `Br`, `Hr`, `Meta`, `Link`, `Script`, `Style`, and 90+ more.

### Class Convenience Methods

Most elements have a `*Class` variant where the first argument is the class attribute:

```go
b.DivClass("container")           // <div class="container">
b.PClass("intro", "id", "p1")     // <p class="intro" id="p1">
b.ButtonClass("btn primary")      // <button class="btn primary">
```

### Utility Functions

| Function                                | Description                              |
| --------------------------------------- | ---------------------------------------- |
| `b.T(strings...)`                       | Write raw text directly to buffer        |
| `b.Wrap(func())`                        | Execute arbitrary Go code in render tree |
| `element.ForEach(slice, func(item))`    | Generic iteration helper                 |
| `element.RenderComponents(b, comps...)` | Render multiple components               |

### Output Methods

| Method       | Description                          |
| ------------ | ------------------------------------ |
| `b.String()` | Get HTML as string                   |
| `b.Bytes()`  | Get HTML as []byte (better for HTTP) |
| `b.Reset()`  | Clear buffer for reuse               |

## Common Patterns

### Attributes as Key-Value Pairs

Attributes are passed as alternating key-value string pairs:

```go
b.Div("id", "main", "class", "container", "data-role", "content").R()
// Output: <div id="main" class="container" data-role="content"></div>

b.A("href", "/about", "class", "nav-link").T("About Us")
// Output: <a href="/about" class="nav-link">About Us</a>
```

### Mixed Content (Text and Elements)

Use `b.T()` to inject text alongside child elements:

```go
b.P().R(
    b.T("This is "),
    b.Strong().T("important"),
    b.T(" information."),
)
// Output: <p>This is <strong>important</strong> information.</p>
```

### Conditional Rendering with Wrap()

`Wrap()` executes arbitrary Go code within the render tree:

```go
b.Div().R(
    b.H2().T("Items"),
    b.Wrap(func() {
        if len(items) == 0 {
            b.P().T("No items found")
        } else {
            for _, item := range items {
                b.Li().T(item)
            }
        }
    }),
)
```

### Iteration with ForEach()

`ForEach` is a generic helper for slices:

```go
items := []string{"Apple", "Banana", "Cherry"}

b.Ul().R(
    element.ForEach(items, func(item string) {
        b.Li().T(item)
    }),
)
// Output: <ul><li>Apple</li><li>Banana</li><li>Cherry</li></ul>
```

### Standard Components

A growing list of ready-to-use components will be found in [element/components](https://github.com/rohanthewiz/element/tree/master/components)

### Custom Components

Implement the `Component` interface for reusable HTML fragments:

```go
// Define the component
type Card struct {
    Title   string
    Content string
}

func (c Card) Render(b *element.Builder) (x any) {
    b.DivClass("card").R(
        b.H3().T(c.Title),
        b.P().T(c.Content),
    )
    return
}

// Use the component
card := Card{Title: "Hello", Content: "World"}
b.Div().R(
    card.Render(b),
)
```

### Building Tables

```go
b.Table().R(
    b.THead().R(
        b.Tr().R(
            b.Th().T("Name"),
            b.Th().T("Value"),
        ),
    ),
    b.TBody().R(
        element.ForEach(rows, func(row DataRow) {
            b.Tr().R(
                b.Td().T(row.Name),
                b.Td().T(row.Value),
            )
        }),
    ),
)
```

### Forms

```go
b.Form("method", "post", "action", "/submit").R(
    b.Label("for", "email").T("Email:"),
    b.Input("type", "email", "id", "email", "name", "email"),
    b.Br(),
    b.Label("for", "password").T("Password:"),
    b.Input("type", "password", "id", "password", "name", "password"),
    b.Br(),
    b.ButtonClass("btn", "type", "submit").T("Submit"),
)
```

### HTTP Handler with Pooling

```go
func handler(w http.ResponseWriter, r *http.Request) {
    // Acquire builder from pool to reduce GC pressure
    b := element.AcquireBuilder()
    defer element.ReleaseBuilder(b)

    b.Html().R(
        b.Body().R(
            b.H1().T("Hello"),
        ),
    )

    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    w.Write(b.Bytes())  // Use Bytes() to avoid string allocation
}
```

### Full Page with HtmlPage Helper

```go
type PageBody struct {
    Title string
}

func (pb PageBody) Render(b *element.Builder) (x any) {
    b.H1().T(pb.Title)
    b.P().T("Welcome to the page")
    return
}

func generatePage() string {
    b := element.B()
    b.HtmlPage(
        "body { font-family: sans-serif; }",  // CSS
        "<title>My Page</title>",              // Head content (raw HTML), minus the '<style>' tag
        PageBody{Title: "Home"},               // Body component
    )
    return b.String()
}
```

## Anti-Patterns

### ❌ Forgetting to Terminate Elements

```go
// WRONG: Missing termination - closing tag never written
b.Div("id", "container")
b.P().T("Content")
// Output: <div id="container"><p>Content</p>  (missing </div>)

// CORRECT: Always terminate with R(), T(), or F()
b.Div("id", "container").R(
    b.P().T("Content"),
)
// Output: <div id="container"><p>Content</p></div>
```

### ❌ Odd Number of Attributes

```go
// WRONG: Odd attribute count - "disabled" has no value
b.Button("class", "btn", "disabled").T("Click")
// The last attribute is dropped

// CORRECT: Use empty string for boolean-like attributes
b.Button("class", "btn", "disabled", "").T("Click")
// Output: <button class="btn" disabled="">Click</button>

// OR: Use the attribute name as its value
b.Button("class", "btn", "disabled", "disabled").T("Click")
// Output: <button class="btn" disabled="disabled">Click</button>
```

### ❌ Using T() When You Need R()

```go
// WRONG: T() only accepts text, not child elements
b.Div().T(b.P().T("Content"))  // This won't work as expected

// CORRECT: Use R() for child elements
b.Div().R(
    b.P().T("Content"),
)
```

### ❌ Creating Elements Without a Builder

```go
// WRONG: Don't try to create elements directly
// There is no public Element constructor

// CORRECT: Always use a Builder
b := element.B()
b.Div().R()
```

### ❌ Not Using Pooling in High-Throughput Handlers

```go
// SUBOPTIMAL: Creates GC pressure under load
func handler(w http.ResponseWriter, r *http.Request) {
    b := element.B()  // New allocation every request
    // ...
}

// BETTER: Use pooling for high-traffic handlers
func handler(w http.ResponseWriter, r *http.Request) {
    b := element.AcquireBuilder()
    defer element.ReleaseBuilder(b)
    // ...
}
```

### ❌ Using String() When Writing to io.Writer

```go
// SUBOPTIMAL: Unnecessary string allocation
w.Write([]byte(b.String()))

// BETTER: Use Bytes() directly
w.Write(b.Bytes())
```

### ❌ Forgetting to Reset Reused Builders

```go
// WRONG: Reusing without reset appends to existing content
b := element.B()
b.P().T("First")
html1 := b.String()  // "<p>First</p>"

b.P().T("Second")
html2 := b.String()  // "<p>First</p><p>Second</p>" - probably not intended

// CORRECT: Reset before reuse
b.Reset()
b.P().T("Second")
html2 := b.String()  // "<p>Second</p>"
```

### Note

- Builder has no method `Textarea()` it is `TextArea()`

## Integration Notes

### With github.com/rohanthewiz/rweb

Element integrates naturally with the rweb web framework:

```go
import (
    "github.com/rohanthewiz/element"
    "github.com/rohanthewiz/rweb"
)

func main() {
		server := rweb.NewServer(rweb.ServerOptions{
			Address: "localhost:8080",
		})

    server.Get("/", func(ctx *rweb.Context) error {
        b := element.AcquireBuilder()
        defer element.ReleaseBuilder(b)

        b.Html().R(
            b.Head().R(
                b.Title().T("Home"),
            ),
            b.Body().R(
                b.H1().T("Welcome to RWeb + Element"),
            ),
        )

        return ctx.WriteHTML(b.String())
    })

    server.Run()
}
```

### With Standard net/http

```go
func handler(w http.ResponseWriter, r *http.Request) {
    b := element.AcquireBuilder()
    defer element.ReleaseBuilder(b)

    b.Html().R(
        b.Body().R(
            b.H1().T("Hello"),
        ),
    )

    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    w.Write(b.Bytes())
}
```

### With HTMX

Element works well for HTMX partial responses:

```go
// Handler returning an HTMX partial
func itemsHandler(w http.ResponseWriter, r *http.Request) {
    b := element.AcquireBuilder()
    defer element.ReleaseBuilder(b)

    items := getItems()
    b.Ul("id", "item-list").R(
        element.ForEach(items, func(item Item) {
            b.Li().R(
                b.Span().T(item.Name),
                b.Button(
                    "hx-delete", fmt.Sprintf("/items/%d", item.ID),
                    "hx-target", "closest li",
                    "hx-swap", "outerHTML",
                ).T("Delete"),
            )
        }),
    )

    w.Write(b.Bytes())
}
```

## Debug Mode

Enable debug mode during development to catch common mistakes:

```go
// Enable debug mode
element.DebugSet()

// Build your HTML...

// Check for issues (returns diagnostic string)
issues := element.DebugShow()

// Clear debug mode
element.DebugClear()
```

Debug mode detects:

- Unclosed tags
- Odd number of attributes
- Children on self-closing elements
- Unwrapped text content

## Quick Reference

| Task                  | Code                                        |
| --------------------- | ------------------------------------------- |
| Create builder        | `b := element.B()`                          |
| Pooled builder        | `b := element.AcquireBuilder()`             |
| Element with children | `b.Div().R(children...)`                    |
| Element with text     | `b.P().T("text")`                           |
| Formatted text        | `b.P().F("Count: %d", n)`                   |
| Add class easily      | `b.DivClass("name")`                        |
| Multiple attributes   | `b.A("href", "/", "class", "link")`         |
| Raw text in tree      | `b.T("some text")`                          |
| Conditional logic     | `b.Wrap(func() { if x { ... } })`           |
| Iterate slice         | `element.ForEach(items, func(i T) { ... })` |
| Render component      | `comp.Render(b)`                            |
| Multiple components   | `element.RenderComponents(b, c1, c2)`       |
| Get HTML string       | `b.String()`                                |
| Get HTML bytes        | `b.Bytes()`                                 |
| Reset builder         | `b.Reset()`                                 |

## Links

- **Repository:** https://github.com/rohanthewiz/element
- **Go Package:** https://pkg.go.dev/github.com/rohanthewiz/element

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.