htmlgo is a type-safe Go library for generating HTML on the server side. It provides a fluent, builder-pattern API for constructing HTML components programmatically.
Package: github.com/theplant/htmlgo
All HTML elements implement this interface:
type HTMLComponent interface {
MarshalHTML(ctx context.Context) ([]byte, error)
}Recommended to use dot import for cleaner code:
import . "github.com/theplant/htmlgo"Text Rendering (auto-escaped):
Text(string)- Escapes HTML entitiesTextf(format, ...args)- Formatted text with escapingRawHTML(string)- Unescaped HTML string
Output:
Fprint(w io.Writer, root HTMLComponent, ctx context.Context)- Write to writerMustString(root HTMLComponent, ctx context.Context)- Convert to string (panics on error)
Container Elements (accept children):
Div(...children)
Span(text)
A(...children)
Body(...children)
Head(...children)
Section(...children)
Article(...children)
Header(...children)
Footer(...children)
Nav(...children)
Ul(...children), Ol(...children), Li(...children)
Table(...children), Thead(...children), Tbody(...children), Tr(...children), Td(...children), Th(text)
Form(...children)
Fieldset(...children)
Select(...children), Option(text)Text Elements (accept text):
H1(text), H2(text), H3(text), H4(text), H5(text), H6(text)
P(...children)
Button(label)
Label(text)
Textarea(text)
Strong(text), Em(text), B(text), I(text)
Code(text), Pre(text)Self-Closing Elements:
Br()
Hr()
Img(src)
Input(name)
Meta()
Link(href)Special Elements:
HTML(...children)- Adds<!DOCTYPE html>automaticallyScript(jsCode)- Wraps in script tag with type="text/javascript"Style(cssCode)- Wraps in style tag with type="text/css"
Attributes:
.Attr(key, value, ...) // Set any attribute (variadic pairs)
.AttrIf(key, value, condition) // Conditional attribute
.Id(string)
.Class(...string) // Add classes (space-separated supported)
.ClassIf(string, bool) // Conditional class
.Data(key, value, ...) // data-* attributes
.Href(string), .Src(string), .Alt(string)
.Name(string), .Value(string), .Type(string)
.Placeholder(string), .Title(string)
.Action(string), .Method(string)
.For(string), .Role(string), .Target(string)
.Disabled(bool), .Checked(bool), .Required(bool), .Readonly(bool)
.TabIndex(int)Styling:
.Style(string) // Add inline styles (semicolon-separated)
.StyleIf(string, bool) // Conditional stylesContent Manipulation:
.Text(string) // Set text content (escaped)
.Children(...HTMLComponent) // Replace children
.AppendChildren(...HTMLComponent)
.PrependChildren(...HTMLComponent)Advanced:
.SetAttr(key, value) // Programmatically set attribute
.Tag(string) // Set tag name
.OmitEndTag() // For self-closing tagsAttributes accept multiple types:
string,[]byte,[]rune- Used as-isint,uint,floattypes - Converted to stringbool- If true, renders as boolean attribute; if false, omitted- Other types - JSON-encoded
ComponentFunc:
ComponentFunc(func(ctx context.Context) ([]byte, error))Custom Builder Pattern:
type MyBuilder struct {
field1 string
field2 int
}
func (b *MyBuilder) Field1(v string) *MyBuilder {
b.field1 = v
return b
}
func (b *MyBuilder) MarshalHTML(ctx context.Context) ([]byte, error) {
return Div(Text(b.field1)).MarshalHTML(ctx)
}If/ElseIf/Else (with pre-evaluated components):
If(condition, component1, component2).
ElseIf(condition2, component3).
Else(component4)Iff/ElseIf/Else (with lazy evaluation):
Iff(condition, func() HTMLComponent {
return Div(Text("Lazy evaluated"))
}).ElseIf(condition2, func() HTMLComponent {
return Span("Alternative")
}).Else(func() HTMLComponent {
return Text("Default")
})Use Iff when: Body depends on condition or expensive to compute.
Pass data through context:
ctx := context.WithValue(context.TODO(), "user", userData)
ComponentFunc(func(ctx context.Context) ([]byte, error) {
if user, ok := ctx.Value("user").(*User); ok {
return Div(Text(user.Name)).MarshalHTML(ctx)
}
return nil, nil
})HTMLComponents (multiple components):
Components(comp1, comp2, comp3)Layout Pattern:
func layout(content HTMLComponent) HTMLComponent {
return HTML(
Head(Meta().Charset("utf8")),
Body(header(), content, footer()),
)
}HTML(
Head(Title("Page")),
Body(Div(Text("Content")))
)Form(
Input("email").Type("email").Required(true),
Button("Submit").Type("submit"),
).Action("/submit").Method("post")items := []string{"A", "B", "C"}
children := make([]HTMLComponent, len(items))
for i, item := range items {
children[i] = Li(Text(item))
}
Ul(children...)Div().
Class("base-class").
ClassIf("active", isActive).
ClassIf("error", hasError)Div().Data("user-id", "123", "role", "admin")
// Renders: <div data-user-id='123' data-role='admin'>func handler(w http.ResponseWriter, r *http.Request) {
user := getUserFromSession(r)
ctx := context.WithValue(context.TODO(), "user", user)
page := HTML(
Head(Title("My Page")),
Body(Div(Text("Hello"))),
)
Fprint(w, page, ctx)
}- Text is auto-escaped via
Text()- useRawHTML()for unescaped content - Context is required for MarshalHTML - use
context.TODO()if not needed - Nil children are filtered automatically
- Boolean attributes:
truerenders attribute,falseomits it - Styles are concatenated with semicolons automatically
- Classes are space-split and deduplicated
- Attributes use single quotes in output:
<div class='myclass'>
- Uses
sync.Poolfor buffer reuse - Efficient string building with
bytes.Buffer - Minimal allocations for common operations
| Task | Code |
|---|---|
| Escaped text | Text("content") |
| Raw HTML | RawHTML("<svg>...</svg>") |
| Container | Div(child1, child2) |
| Attributes | .Attr("key", "value") |
| Classes | .Class("class1 class2") |
| Styles | .Style("color:red; font-size:14px") |
| Conditional | Iff(cond, func() HTMLComponent {...}) |
| Custom component | ComponentFunc(func(ctx) ([]byte, error) {...}) |
| Output | Fprint(w, component, ctx) |