gomponents are HTML components 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 github.com/maragudk/gomponents
See gomponents on Github or buy the Official gomponents <marquee> Element 🤯🤩
Into video? See this lightning talk from GopherCon 2021.
Have a look at this component. If you know HTML, you know what it does. Easy, right?
func Navbar() g.Node {
return Nav(Class("navbar"),
Ol(
Li(A(Href("/"), g.Text("Home"))),
Li(A(Href("/contact"), g.Text("Contact"))),
Li(A(Href("/about"), g.Text("About"))),
),
)
}
Let's deduplicate a bit.
func Navbar() g.Node {
return Nav(Class("navbar"),
Ol(
NavbarItem("Home", "/"),
NavbarItem("Contact", "/contact"),
NavbarItem("About", "/about"),
),
)
}
func NavbarItem(name, path string) g.Node {
return Li(A(Href(path), g.Text(name)))
}
Sometimes you only want to show a component based on some condition. Enter If
:
func Navbar(loggedIn bool) g.Node {
return Nav(Class("navbar"),
Ol(
NavbarItem("Home", "/"),
NavbarItem("Contact", "/contact"),
NavbarItem("About", "/about"),
g.If(loggedIn,
NavbarItem("Log out", "/logout"),
),
),
)
}
func NavbarItem(name, path string) g.Node {
return Li(A(Href(path), g.Text(name)))
}
What if you have data and want to map it to components? No problem.
type NavLink struct {
Name string
Path string
}
func Navbar(loggedIn bool, links []NavLink) g.Node {
return Nav(Class("navbar"),
Ol(
g.Group(g.Map(links, func(l NavLink) g.Node {
return NavbarItem(l.Name, l.Path)
})),
g.If(loggedIn,
NavbarItem("Log out", "/logout"),
),
),
)
}
func NavbarItem(name, path string) g.Node {
return Li(A(Href(path), g.Text(name)))
}
Want to apply CSS classes based on state? Use the Classes
helper.
type NavLink struct {
Name string
Path string
}
func Navbar(loggedIn bool, links []NavLink, currentPath string) g.Node {
return Nav(Class("navbar"),
Ol(
g.Group(g.Map(links, func(l NavLink) g.Node {
return NavbarItem(l.Name, l.Path, l.Path == currentPath)
})),
g.If(loggedIn,
NavbarItem("Log out", "/logout", false),
),
),
)
}
func NavbarItem(name, path string, active bool) g.Node {
return Li(A(Href(path), g.Text(name)), c.Classes{
"navbar-item": true,
"active": active,
"inactive": !active,
})
}
Miss those <tags>
or need to inject HTML from somewhere else? Use Raw
.
type NavLink struct {
Name string
Path string
}
func Navbar(loggedIn bool, links []NavLink, currentPath string) g.Node {
return Nav(Class("navbar"),
Ol(
g.Raw(`<span class="logo"><img src="logo.png></span>"`),
g.Group(g.Map(links, func(l NavLink) g.Node {
return NavbarItem(l.Name, l.Path, l.Path == currentPath)
})),
g.If(loggedIn,
NavbarItem("Log out", "/logout", false),
),
),
)
}
func NavbarItem(name, path string, active bool) g.Node {
return Li(A(Href(path), g.Text(name)), c.Classes{
"navbar-item": true,
"active": active,
"inactive": !active,
})
}
Your editor helps you with auto-completion. It's type-safe. Nice formatting using gofmt. And all common HTML elements and attributes are included!
$ go get github.com/maragudk/gomponents
Sometimes there's nothing like seeing it in action. Try running this complete program and going to localhost:8080 .
package main
import (
"errors"
"log"
"net/http"
"time"
g "github.com/maragudk/gomponents"
c "github.com/maragudk/gomponents/components"
. "github.com/maragudk/gomponents/html"
)
func main() {
http.Handle("/", createHandler(indexPage()))
http.Handle("/contact", createHandler(contactPage()))
http.Handle("/about", createHandler(aboutPage()))
if err := http.ListenAndServe("localhost:8081", nil); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Println("Error:", err)
}
}
func createHandler(title string, body g.Node) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Rendering a Node is as simple as calling Render and passing an io.Writer
_ = Page(title, r.URL.Path, body).Render(w)
}
}
func indexPage() (string, g.Node) {
return "Welcome!", Div(
H1(g.Text("Welcome to this example page")),
P(g.Text("I hope it will make you happy. 😄 It's using TailwindCSS for styling.")),
)
}
func contactPage() (string, g.Node) {
return "Contact", Div(
H1(g.Text("Contact us")),
P(g.Text("Just do it.")),
)
}
func aboutPage() (string, g.Node) {
return "About", Div(
H1(g.Text("About this site")),
P(g.Text("This is a site showing off gomponents.")),
)
}
func Page(title, path string, body g.Node) g.Node {
// HTML5 boilerplate document
return c.HTML5(c.HTML5Props{
Title: title,
Language: "en",
Head: []g.Node{
Script(Src("https://cdn.tailwindcss.com?plugins=typography")),
},
Body: []g.Node{
Navbar(path, []PageLink{
{Path: "/contact", Name: "Contact"},
{Path: "/about", Name: "About"},
}),
Container(
Prose(body),
PageFooter(),
),
},
})
}
type PageLink struct {
Path string
Name string
}
func Navbar(currentPath string, links []PageLink) g.Node {
return Nav(Class("bg-gray-700 mb-4"),
Container(
Div(Class("flex items-center space-x-4 h-16"),
NavbarLink("/", "Home", currentPath == "/"),
// We can Map custom slices to Nodes
g.Group(g.Map(links, func(l PageLink) g.Node {
return NavbarLink(l.Path, l.Name, currentPath == l.Path)
})),
),
),
)
}
// NavbarLink is a link in the Navbar.
func NavbarLink(path, text string, active bool) g.Node {
return A(Href(path), g.Text(text),
// Apply CSS classes conditionally
c.Classes{
"px-3 py-2 rounded-md text-sm font-medium focus:outline-none focus:text-white focus:bg-gray-700": true,
"text-white bg-gray-900": active,
"text-gray-300 hover:text-white hover:bg-gray-700": !active,
},
)
}
func Container(children ...g.Node) g.Node {
return Div(Class("max-w-7xl mx-auto px-2 sm:px-6 lg:px-8"), g.Group(children))
}
func Prose(children ...g.Node) g.Node {
return Div(Class("prose"), g.Group(children))
}
func PageFooter() g.Node {
return Footer(Class("prose prose-sm prose-indigo"),
P(
// We can use string interpolation directly, like fmt.Sprintf.
g.Textf("Rendered %v. ", time.Now().Format(time.RFC3339)),
// Conditional inclusion
g.If(time.Now().Second()%2 == 0, g.Text("It's an even second.")),
g.If(time.Now().Second()%2 == 1, g.Text("It's an odd second.")),
),
P(A(Href("https://www.gomponents.com"), g.Text("gomponents"))),
)
}
See also the Github repository or the blog post that started it all.