Alpine.js and Tailwind HTML setup
Plain HTML with Alpine and Tailwind CSS
Today I set up a Go project using Tailwind and Alpine.js. This web application uses Go templates so there was no need for any React or other javascript frontend. I did want dropdowns and to use tailwind without a CDN. This meant I needed to install some NPM packages and include some javascript to facilitate the dropdowns.
This is the minimum requirement to get a basic Tailwind and Alpine system set up for a simple Go web application - it could extend to any other language but is not tested.
Pre-amble
This project is laid out with the static files and html inside a directory named ui
within
their own directories.
Before the setup and initialisation of Alpine and Tailwind, the directory looks something like this.
ui
├── html
│  ├── base.layout.tmpl
│  ├── footer.partial.tmpl
│  ├── home.page.tmpl
│  └── navbar.partial.tmpl
├── static
│  ├── css
│  │  └── main.css
│  └── js
└── ui.go
Installing and compiling Tailwind
First thing to do is install Tailwind. I use yarn
but I’ve used npm
here because its more
widely used.
npm install -D tailwindcss
# I also installed these tailwind plugins which I needed for the layout
#npm install -D @tailwindcss/forms @tailwind/aspect-ration
npx tailwindcss init
This creates a tailwind.config.js
file which needs to be updated to reflect our directory
structure and template file types.
module.exports = {
content: ["./ui/**/*tmpl"], <-- THIS IS IMPORTANT
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/aspect-ratio'),
require('@tailwindcss/forms'),
],
}
What tripped me up was the content: []
block. My Go templates use the following naming
convention; <name>.<type>.tmpl
. The types are layout
,page
and partial
but it always ends
with tmpl
. Initially, I was using .html
because I wasn’t paying attention and copying other
guides. This time I am catching all tmpl
files inside the ui
folder.
I then created a npm script
block entry into the package.json
file.
{
"scripts": {
"build-css": "tailwindcss -i ui/static/css/main.css -o ui/static/css/theme.css -m",
},
"devDependencies": {
"@tailwindcss/aspect-ratio": "^0.4.0",
"@tailwindcss/forms": "^0.5.0",
}
}
build-css
will create a file called theme.css
in the ui/static/css
directory which is then
referenced inside my base.layout.tmpl
template. The -m
minifies it, which I do for prod and
dev because this file is never touched.
The above step is predicated on the existence of a file called main.css
inside the
ui/static/css
directory. For clarity my main.css
looks like this:
" https://tailwindcss.com/docs/installation - Step #3
@tailwind base;
@tailwind components;
@tailwind utilities;
Alternatively, you could run just run the compiler (build-css
) in a bash script, CI pipeline
or Dockerfile.
Alpine.js Set up
Next, bundle up Alpine inside the application without needed to use the CDN.
npm install alpinejs esbuild
Alpine needs to be instantiated, so I created an index.js
at the root of the directory and
called Alpine.
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();
Then I add an extra script to the package.json
which will compile Alpine using esbuild and
drop it into the ui/static/js
directory. Alpine, like tailwind is minified because I am not
altering it in any way.
{
"scripts": {
"build-css": "tailwindcss -i ui/static/css/main.css -o ui/static/css/theme.css -m",
"alpine": "npx esbuild index.js --outfile=ui/static/js/bundle.js --bundle --minify" # New
},
"devDependencies": {
"@tailwindcss/aspect-ratio": "^0.4.0",
"@tailwindcss/forms": "^0.5.0",
"tailwindcss": "^3.0.23"
},
"dependencies": {
"alpinejs": "^3.9.1", # New
"esbuild": "^0.14.25" # New
}
}
The final directory structure should look something like this
ui
├── html
│  ├── base.layout.tmpl
│  ├── footer.partial.tmpl
│  ├── home.page.tmpl
│  └── navbar.partial.tmpl
├── static
│  ├── css
│  │  ├── main.css
│  │  └── theme.css # New
│  └── js
│  └── bundle.js # New
└── ui.go
Testing it out
Remove any references to Tailwind or Alpine’s CDN and replace them with the location of theme. css
and bundle.js
respectively. Refreshing the application page should render everything
correctly and any Alpine widgets should work.
The best part is the application is completely portable. For clarity, this application is using
Go embedded files so that it is
self-contained. After running go build
you will be able to run it completely isolated and have
the CSS and JS work as expected.
Keep up to date with my stuff
Subscribe to get new posts and retrospectives