Joe Davidson

Generating Tailwind CSS files for templ templates

gohtmltailwindtempl

Introduction #

Tailwind CSS provides a CLI for creating a minified CSS file containing only the classes required by your web templates.

In this post we will look at combining a simple templ web page with the Tailwind CSS Standalone CLI.

For more information on templ see: https://templ.guide

Installation #

There are a couple of tools we will need to be installed, this post assumes you already have Go installed (the post was written using 1.20).

Templ

Install the templ command using go install.

go install github.com/a-h/templ/cmd/templ@v0.2.304

Tailwind CSS Standalone CLI

Install the tailwindcss command by downloading the latest release.

https://github.com/tailwindlabs/tailwindcss/releases

Boilerplate #

We will start with a new go module.

$ mkdir templ-tailwind
$ cd templ-tailwind
$ go mod init github.com/joerdav/templ-tailwind

Templ #

First, let's create a very simple templ template, that displays the current time.

For brevity I am going to put all the code in a single main package.

// home.templ

package main

import "time"

templ home() {
<html>
<body class="bg-white">
<div class="relative isolate px-6 pt-14 lg:px-8">
<div class="text-center">
<h1 class="text-4xl font-bold tracking-tight text-gray-900 sm:text-6xl">
The current time is: { time.Now().Format(time.RFC3339) }
</h1>
</div>
</div>
</body>
</html>
}

This template is mostly Tailwind styling, and then in the <h1> we print the current time.

Now, templ requires a generation step, we can do this using templ generate. For ease we can also hook into the go generate ./... command by adding the following file.

// gen.go

package main

//go:generate templ generate

Now run go generate ./....

$ go generate ./...
home.templ complete in 23.683327ms
Generated code for 1 templates with 0 errors in 24.470467ms

We now have a generated home_templ.go file.

.
├── gen.go
├── go.mod
├── go.sum
├── home.templ
└── home_templ.go

Serving the page #

We now need to serve this page up, we can use net/http for this. templ also provides a wrapper to create a http.Handler from a templ.Component, by doing templ.Handler(home()).

// main.go

package main

import (
"log"
"net/http"

"github.com/a-h/templ"
)

func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}

func run() error {
http.Handle("/", templ.Handler(home()))
return http.ListenAndServe("localhost:8080", nil)
}

Let's give it a run with go run ., and we can see our page but with no styling!

Ugly Styling

Generate CSS #

Now we have our template we can get tailwindcss to generate some styles for us. First, we need to initialise.

tailwindcss init

This will create a config file for us.

.
├── gen.go
├── go.mod
├── go.sum
├── home.templ
├── home_templ.go
├── main.go
└── tailwind.config.js

And we can create a folder for our static assets.

$ mkdir css

Tailwind requires us to specify where our HTML templates live. We will specify all templ files in our project.

// tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
content: [ "./*.templ" ],
theme: {
extend: {},
},
plugins: [],
}

Now let's generate our css:

$ tailwindcss -o css/styles.css --minify

This will create a css file that only contains the code we need for our website.

.
├── css
│   └── styles.css
├── gen.go
├── go.mod
├── go.sum
├── home.templ
├── home_templ.go
├── main.go
└── tailwind.config.js

We could even include this as a generate step.

// gen.go

package main

//go:generate templ generate
//go:generate tailwindcss -o css/styles.css --minify

Tie it all together #

Now we have our page and our css file, we just need to link it all together. We can host our css folder as part of our server using the embed package. This way our app can be shipped as a single binary!

// main.go

package main

import (
"embed"
"log"
"net/http"

"github.com/a-h/templ"
)

//go:embed css
var FS embed.FS

func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}

func run() error {
http.Handle("/", templ.Handler(home()))
http.Handle("/public/", http.StripPrefix("/public", http.FileServer(http.FS(FS))))
return http.ListenAndServe("localhost:8080", nil)
}

Finally, let's reference this from our template.

package main

import "time"

templ home() {
<html>
<head><link rel="stylesheet" href="/public/css/styles.css"/></head>
...

With a final go generate and a go run ., we should have a website with minimal css, distributed as a single binary.

Better Styling

All code for this can be found at https://github.com/joerdav/templ-tailwind