gomponents

package module
v1.8.0 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2025 License: MIT Imports: 5 Imported by: 5

README

Tired of complex template languages?

Logo

GoDoc Go codecov Go Report Card

Try HTML components in pure Go.

gomponents are HTML components written in pure Go. They render to HTML 5, and make it easy for you to build reusable components. So you can focus on building your app instead of learning yet another templating language.

go get alin.ovh/gomponents

Made with ✨sparkles✨ by maragu, forked by alanpearce to add helpers.

Fork changes

  • MapWithIndex and MapMap for mapping over slices and maps respectively
  • If and Iff take an extra argument to render a fallback component when the condition is false
  • Built-in Node implementations also implement io.WriterTo
    • Users of NodeFunc can use NodeWriterFunc to keep compatibility

Features

Check out www.gomponents.com for an introduction.

  • Build reusable HTML components
  • Write declarative HTML 5 in Go without all the strings, so you get
    • Type safety from the compiler
    • Auto-completion from the IDE
    • Easy debugging with the standard Go debugger
    • Automatic formatting with gofmt/goimports
  • Simple API that's easy to learn and use (you know most already if you know HTML)
  • Useful helpers like
    • Text and Textf that insert HTML-escaped text,
    • Raw and Rawf for inserting raw strings,
    • Map for mapping data to components and Group for grouping components,
    • and If/Iff for conditional rendering.
  • No external dependencies
  • Mature and stable, no breaking changes

Usage

go get alin.ovh/gomponents
package main

import (
	. "alin.ovh/gomponents"
	. "alin.ovh/gomponents/components"
	. "alin.ovh/gomponents/html"
)

func Navbar(authenticated bool, currentPath string) Node {
	return Nav(
		NavbarLink("/", "Home", currentPath),
		NavbarLink("/about", "About", currentPath),
		If(authenticated, NavbarLink("/profile", "Profile", currentPath)),
	)
}

func NavbarLink(href, name, currentPath string) Node {
	return A(Href(href), Classes{"is-active": currentPath == href}, g.Text(name))
}

(Some people don't like dot-imports, and luckily it's completely optional.)

For a more complete example, see the examples directory. There's also the gomponents-starter-kit for a full application template.

Architecture

gomponents is organized into several packages:

  • gomponents: Core interfaces and functions like Node, El, Attr, and helpers like Map, Group, If, Text, Raw.
  • gomponents/html: HTML elements and attributes.
  • gomponents/components: Higher-level components and utilities.
  • gomponents/http: HTTP-related utilities for web servers.
Void Elements

Void elements in HTML (like <br>, <img>, <input>) don't have closing tags. gomponents handles these correctly by checking against an internal list of void elements during rendering. When you create a void element, any child nodes that are not attributes will be ignored automatically to ensure valid HTML output.

Performance Considerations

gomponents renders directly to an io.Writer, making it efficient for server-side rendering. The library avoids unnecessary allocations where possible.

FAQ

Is gomponents production-ready?

Yes! gomponents is mature, stable, fully tested with 100% coverage, and has been used in production by myself and many others.

Should I choose html/template, Templ, or gomponents?

These are all good choices, and it largely comes down to preference. I wrote gomponents because I didn't like how I think it's hard to pass data around between templates in html/template. gomponents is pure Go, with no extra build step like Templ, so it works with all tools that already support Go.

That said, both html/template and Templ will do the same thing as gomponents in the end. Try them all and choose what you like!

I don't like how HTML looks in Go.

First of all, that's not a question. 😉

More seriously, think of gomponents like a DSL for HTML. You're building UI components. Give it a day, and it'll feel natural.

I'd like to add feature X, can I do that?

First of all, thank you for wanting to contribute! 😊 See CONTRIBUTING.md for a guide.

I accept code contributions, especially with new HTML elements and attributes. I always welcome issues discussing interesting aspects of gomponents, and obviously bug reports and the like. But otherwise, I consider gomponents pretty much feature complete.

New features to the core library are unlikely to be merged, since I like keeping it simple and the API small. In particular, new functions around collections (similar to Map) or flow control (IfElse/Else) will not be added. Map was introduced before generics were a thing, and I think it's better to start using generic functions from the stdlib or other libraries, instead of adding gomponents-specific variations of them to this library.

If there's something missing that you need, I would recommend to keep small helper functions around in your own projects. And if all else fails, you can always use an IIFE:

func list(ordered bool) Node {
	return func() Node {
		// Do whatever you need to do, imperatively
		if ordered {
			return Ol()
		} else {
			return Ul()
		}
	}()
}
What's up with the specially named elements and attributes?

Unfortunately, there are some name clashes in HTML elements and attributes, so they need an El or Attr suffix, to be able to co-exist in the same package in Go.

I've chosen one or the other based on what I think is the common usage. In either case, the less-used variant also exists in the codebase:

  • cite (Cite/CiteAttr, CiteEl also exists)
  • data (DataEl/Data, DataAttr also exists)
  • form (Form/FormAttr, FormEl also exists)
  • label (Label/LabelAttr, LabelEl also exists)
  • style (StyleEl/Style, StyleAttr also exists)
  • title (TitleEl/Title, TitleAttr also exists)
Example with `Style` and `StyleEl`
package html

import (
	. "maragu.dev/gomponents"
	. "maragu.dev/gomponents/components"
	. "maragu.dev/gomponents/html"
)

func MyPage() Node {
	return HTML5(HTML5Props{
		Title: "My Page",
		Head: []Node{
			StyleEl(g.Raw("body {background-color: #fff; }")),
		},
		Body: []Node{
			H1(Style("color: #000"), Text("My Page")),
		},
	})
}

Documentation

Overview

Package gomponents provides HTML components in Go, that render to HTML 5.

The primary interface is a Node. It defines a function Render, which should render the Node to the given writer as a string.

All DOM elements and attributes can be created by using the El and Attr functions.

The functions Text, Textf, Raw, and Rawf can be used to create text nodes, either HTML-escaped or unescaped.

See also helper functions Map, If, and Iff for mapping data to nodes and inserting them conditionally.

There's also the Group type, which is a slice of Node-s that can be rendered as one Node.

For basic HTML elements and attributes, see the package html.

For higher-level HTML components, see the package components.

For HTTP helpers, see the package http.

Index

Examples

Constants

View Source
const (
	ElementType = NodeType(iota)
	AttributeType
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Group

type Group []Node

Group a slice of Node-s into one Node, while still being usable like a regular slice of Node-s. A Group can render directly, but if any of the direct children are AttributeType, they will be ignored, to not produce invalid HTML.

Example
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	children := []g.Node{g.El("div"), g.El("span")}
	e := g.Group(children)
	_ = e.Render(os.Stdout)
}
Output:

<div></div><span></span>
Example (Slice)
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	e := g.Group{g.El("div"), g.El("span")}
	_ = e.Render(os.Stdout)
}
Output:

<div></div><span></span>

func Map

func Map[T any](ts []T, cb func(T) Node) Group

Map a slice of anything to a Group (which is just a slice of Node-s).

Example
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	items := []string{"party hat", "super hat"}
	e := g.El("ul", g.Map(items, func(i string) g.Node {
		return g.El("li", g.Text(i))
	}))
	_ = e.Render(os.Stdout)
}
Output:

<ul><li>party hat</li><li>super hat</li></ul>

func MapMap

func MapMap[K comparable, T any](ts map[K]T, cb func(K, T) Node) Group

Map a map of anything to a Group (which is just a slice of Node-s).

Example
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	items := map[string]string{"party": "hat"}
	e := g.El("ul", g.MapMap(items, func(key string, value string) g.Node {
		return g.El("li", g.Textf("%v: %v", key, value))
	}))
	_ = e.Render(os.Stdout)
}
Output:

<ul><li>party: hat</li></ul>

func MapWithIndex

func MapWithIndex[T any](ts []T, cb func(int, T) Node) Group

Map a slice of anything to a Group (which is just a slice of Node-s).

Example
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	items := []string{"party hat", "super hat"}
	e := g.El("ul", g.MapWithIndex(items, func(index int, item string) g.Node {
		return g.El("li", g.Textf("%v: %v", index, item))
	}))
	_ = e.Render(os.Stdout)
}
Output:

<ul><li>0: party hat</li><li>1: super hat</li></ul>

func (Group) Render

func (g Group) Render(w io.Writer) error

Render satisfies Node.

func (Group) String

func (g Group) String() string

String satisfies fmt.Stringer.

func (Group) WriteTo added in v1.8.0

func (g Group) WriteTo(w io.Writer) (int64, error)

WriteTo satisfies io.Writer.

type IterNode

type IterNode struct {
	iter.Seq[Node]
}

func MapIter

func MapIter[T any](ts iter.Seq[T], cb func(T) Node) IterNode

Map an iterator of anything to an IterNode (which is just an iter.Seq of Node-s)

Example
package main

import (
	"os"
	"slices"

	g "alin.ovh/gomponents"
)

func main() {
	items := slices.Values([]string{"party hat", "super hat"})
	e := g.El("ul", g.MapIter(items, func(value string) g.Node {
		return g.El("li", g.Text(value))
	}))
	_ = e.Render(os.Stdout)
}
Output:

<ul><li>party hat</li><li>super hat</li></ul>

func MapIter2

func MapIter2[K, V any](ts iter.Seq2[K, V], cb func(K, V) Node) IterNode

Map an iterator of pairs of values to an IterNode (which is just an iter.Seq of Node-s)

Example
package main

import (
	"maps"
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	items := maps.All(map[string]string{"party": "hat", "super": "hat"})
	e := g.El("ul", g.MapIter2(items, func(key string, value string) g.Node {
		return g.El("li", g.Textf("%v: %v", key, value))
	}))
	_ = e.Render(os.Stdout)
}
Output:

<ul><li>party: hat</li><li>super: hat</li></ul>

func (IterNode) Render

func (it IterNode) Render(w io.Writer) error

func (IterNode) WriteTo added in v1.8.0

func (it IterNode) WriteTo(w io.Writer) (int64, error)

type Node

type Node interface {
	Render(w io.Writer) error
}

Node is a DOM node that can Render itself to a io.Writer.

func Attr

func Attr(name string, value ...string) Node

Attr creates an attribute DOM Node with a name and optional value. If only a name is passed, it's a name-only (boolean) attribute (like "required"). If a name and value are passed, it's a name-value attribute (like `class="header"`). More than one value make Attr panic. Use this if no convenience creator exists in the html package.

Example (Bool)
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	e := g.El("input", g.Attr("required"))
	_ = e.Render(os.Stdout)
}
Output:

<input required>
Example (Name_value)
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	e := g.El("div", g.Attr("id", "hat"))
	_ = e.Render(os.Stdout)
}
Output:

<div id="hat"></div>

func El

func El(name string, children ...Node) Node

El creates an element DOM Node with a name and child Nodes. See https://dev.w3.org/html5/spec-LC/syntax.html#elements-0 for how elements are rendered. No tags are ever omitted from normal tags, even though it's allowed for elements given at https://dev.w3.org/html5/spec-LC/syntax.html#optional-tags If an element is a void element, non-attribute children nodes are ignored. Use this if no convenience creator exists in the html package.

Example
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	e := g.El("div", g.El("span"))
	_ = e.Render(os.Stdout)
}
Output:

<div><span></span></div>

func If

func If(condition bool, n Node, otherwise ...Node) Node

If condition is true, return the given Node. Otherwise, return nil. This helper function is good for inlining elements conditionally. If it's important that the given Node is only evaluated if condition is true (for example, when using nilable variables), use Iff instead.

Example
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	showMessage := true

	e := g.El("div",
		g.If(showMessage,
			g.El("span", g.Text("You lost your hat!")),
			g.El("span", g.Text("No messages.")),
		),
	)

	_ = e.Render(os.Stdout)
}
Output:

<div><span>You lost your hat!</span></div>

func Iff

func Iff(condition bool, f func() Node, otherwise ...func() Node) Node

Iff condition is true, call the given function. Otherwise, return nil. This helper function is good for inlining elements conditionally when the node depends on nilable data, or some other code that could potentially panic. If you just need simple conditional rendering, see If.

Example
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	type User struct {
		Name string
	}
	var user *User

	e := g.El("div",
		// This would panic using just If
		g.Iff(
			user != nil,
			func() g.Node {
				return g.Text(user.Name)
			},
			func() g.Node {
				return g.Text("No user")
			},
		),
	)

	_ = e.Render(os.Stdout)
}
Output:

<div>No user</div>

func Raw

func Raw(t string) Node

Raw creates a text DOM Node that just Renders the unescaped string t.

Example
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	e := g.El(
		"span",
		g.Raw(
			`<button onclick="javascript:alert('Party time!')">Party hats</button> &gt; normal hats.`,
		),
	)
	_ = e.Render(os.Stdout)
}
Output:

<span><button onclick="javascript:alert('Party time!')">Party hats</button> &gt; normal hats.</span>

func Rawf

func Rawf(format string, a ...any) Node

Rawf creates a text DOM Node that just Renders the interpolated and unescaped string format.

Example
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	e := g.El(
		"span",
		g.Rawf(
			`<button onclick="javascript:alert('%v')">Party hats</button> &gt; normal hats.`,
			"Party time!",
		),
	)
	_ = e.Render(os.Stdout)
}
Output:

<span><button onclick="javascript:alert('Party time!')">Party hats</button> &gt; normal hats.</span>

func Text

func Text(t string) Node

Text creates a text DOM Node that Renders the escaped string t.

Example
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	e := g.El("span", g.Text("Party hats > normal hats."))
	_ = e.Render(os.Stdout)
}
Output:

<span>Party hats &gt; normal hats.</span>

func Textf

func Textf(format string, a ...any) Node

Textf creates a text DOM Node that Renders the interpolated and escaped string format.

Example
package main

import (
	"os"

	g "alin.ovh/gomponents"
)

func main() {
	e := g.El("span", g.Textf("%v party hats > %v normal hats.", 2, 3))
	_ = e.Render(os.Stdout)
}
Output:

<span>2 party hats &gt; 3 normal hats.</span>

type NodeFunc

type NodeFunc func(io.Writer) error

NodeFunc is a render function that is also a Node of ElementType.

func (NodeFunc) Render

func (n NodeFunc) Render(w io.Writer) error

Render satisfies Node.

func (NodeFunc) String

func (n NodeFunc) String() string

String satisfies fmt.Stringer.

func (NodeFunc) Type

func (n NodeFunc) Type() NodeType

Type satisfies [nodeTypeDescriber].

type NodeType

type NodeType int

NodeType describes what type of Node it is, currently either an ElementType or an AttributeType. This decides where a Node should be rendered. Nodes default to being ElementType.

type NodeWriter added in v1.8.0

type NodeWriter interface {
	Node
	io.WriterTo
}

NodeWriter is a Node that can also be used as an io.WriterTo

type NodeWriterFunc added in v1.8.0

type NodeWriterFunc func(io.Writer) (int64, error)

NodeWriterFunc is a render function that is also a Node of ElementType.

func (NodeWriterFunc) Render added in v1.8.0

func (n NodeWriterFunc) Render(w io.Writer) error

Render satisfies Node.

func (NodeWriterFunc) String added in v1.8.0

func (n NodeWriterFunc) String() string

String satisfies fmt.Stringer.

func (NodeWriterFunc) Type added in v1.8.0

func (n NodeWriterFunc) Type() NodeType

Type satisfies [nodeTypeDescriber].

func (NodeWriterFunc) WriteTo added in v1.8.0

func (n NodeWriterFunc) WriteTo(w io.Writer) (int64, error)

WriteTo satisfies io.WriterTo.

Directories

Path Synopsis
Package components provides high-level components and helpers that are composed of low-level elements and attributes.
Package components provides high-level components and helpers that are composed of low-level elements and attributes.
Package html provides common HTML elements and attributes.
Package html provides common HTML elements and attributes.
Package http provides adapters to render gomponents in http handlers.
Package http provides adapters to render gomponents in http handlers.
internal
assert
Package assert provides testing helpers.
Package assert provides testing helpers.

Jump to

Keyboard shortcuts

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