Building view components with gomponents and TailwindCSS in Go

Written 14th of December 2020

TailwindCSS has taken the CSS framework world by storm. Its inline, utility-first approach to styling with CSS classes makes it perfect for using together with a component library like gomponents, to make it easy to build your server-side-rendered web apps in Go. In this post, I'll show you how.

TailwindCSS explained very briefly

TailwindCSS is a CSS framework to style your HTML. It embraces CSS utility classes that do a small, well-defined thing (like px-4, which makes a padding on the left and right of the component), and lets you build your design out of these building blocks. It's still better than using the style attribute on elements, because you get:

  • Consistent colours, spacing etc.
  • Higher-level CSS, like media queries
  • Cross-browser consistency

For an interesting background of how TailwindCSS came to be, read CSS Utility Classes and "Separation of Concerns" by Adam Wathan.

gomponents + TailwindCSS

gomponents is a small Go library to make it easy to build reusable view components. Coupled with TailwindCSS, you get good-lookin' view components. 😎 Consider the button we all need somewhere in our app:

import (
	g "github.com/maragudk/gomponents"
	c "github.com/maragudk/gomponents/components"
	. "github.com/maragudk/gomponents/html"
)

func NiceButton(text string, primary bool) g.Node {
	return Button(g.Text(text), c.Classes{
		"flex items-center justify-center px-4 py-3 rounded-md": true,
		"text-white bg-indigo-600 hover:bg-indigo-700":          primary,
		"text-indigo-600 bg-gray-50 hover:bg-gray-200":          !primary,
	})
}

Here, we're using a gomponents utility class (Classes) to conditionally apply CSS classes from TailwindCSS, depending on whether it's a primary button or not. The CSS classes adjust the padding, round the button, and set the base and hover colours.

Now, every time we need a NiceButton, we just use the function/component in our views:

import (
	g "github.com/maragudk/gomponents"
	c "github.com/maragudk/gomponents/components"
	. "github.com/maragudk/gomponents/html"
)

func Home() g.Node {
	return Div(Class("max-w-lg flex space-x-8"),
    NiceButton("I'm primary", true),
    NiceButton("I'm just secondary", false),
	)
}

It doesn't take much imagination to see how this makes it really easy to build pages for your next web app, without having to repeat your class string combinations everywhere. Also, this takes me one step closer to not missing building views in React, because I want to build server-side-rendered web apps in Go for a variety of reasons. But that is the topic for another blog post…

If you're interested in seeing a slightly more complex example, I've put together a gomponents-tailwind-example project over on Github. Check it out to see examples of building a page layout, a navigation bar, and how to easily map dynamic data to components.

What do you think of this approach? Let me know on Twitter at @markusrgw.

About me

A picture of Markus.

I'm Markus, a professional software consultant and developer. 🤓✨ You can reach me on markus@maragu.dk or at @markusrgw. If you like my work, consider sponsoring me. You could also check out my LinkedIn profile, or do neither of these things and just grab a coffee and a biscuit.