How to add Tailwind to Hugo
By Flavio Copes
Learn how to add Tailwind CSS v4 to a Hugo theme with the Tailwind CLI, a CSS import, build and watch scripts, and Hugo's asset pipeline.
To add Tailwind CSS to a Hugo site you install it with npm inside your theme, let the Tailwind CLI generate a CSS file into the theme’s assets folder, and load that file from your layouts through Hugo’s asset pipeline. Hugo and Tailwind run as two separate processes, and this setup keeps them nicely decoupled.
This guide uses Tailwind CSS v4. The CLI moved to its own package, @tailwindcss/cli, and there is no tailwind.config.js and no @tailwind directives anymore. If you are on Tailwind v3, the same steps work with the tailwindcss CLI, a content array in tailwind.config.js and the three @tailwind directives.
Install Tailwind in the theme
We work inside the theme folder because that’s where the layouts live, and Tailwind needs to scan them.
Inside your theme folder run
npm init -y
Then install Tailwind CSS and its CLI as development dependencies:
npm install -D tailwindcss @tailwindcss/cli
There is no npx tailwindcss init step in v4. Tailwind scans the folder it runs in for class names on its own, so it finds the theme’s layouts without any configuration.
Create the input CSS file
Now create a tailwind.css file in the theme’s folder and add this:
@import "tailwindcss";
@source "../../content";
That single import replaces the old @tailwind base, @tailwind components, and @tailwind utilities directives. When Tailwind builds, it expands the import into the reset styles and every utility class your templates use.
The @source line matters for Hugo. Tailwind only scans the folder it runs in, which is the theme. The site’s content folder sits two levels up, outside the theme, so Tailwind never sees it. @source "../../content" adds it to the scan, and you can use Tailwind classes in your markdown too. Without that line, a class that only appears in a markdown file never makes it into style.css.
Add the build scripts
Now open package.json and in the scripts section add the build and watch commands:
{
"name": "valley",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "npx @tailwindcss/cli -i ./tailwind.css -o ./assets/style.css",
"watch": "npx @tailwindcss/cli -i ./tailwind.css -o ./assets/style.css --watch"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"tailwindcss": "^4",
"@tailwindcss/cli": "^4"
}
}
We output to assets/style.css because Hugo can process files in the assets folder with its resources functions, which we’ll use next.
Try running
npm run build
and you should see the style.css file!
| Tip: use npm run watch when working on the theme, so the changes are saved to the style.css file on every file save.
In practice this means two terminals while you develop: one running hugo server, one running npm run watch. Hugo picks up the regenerated CSS and reloads the browser.
Load the CSS in your layouts
Now we can include it in our layouts, for example I put it in layouts/partials/header.html
{{ $styles := resources.Get "style.css" }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}">
When a class doesn’t show up
One thing that will bite you: you add a class like mt-8 to a template, and nothing changes.
Two usual causes.
Either the watch process isn’t running, so style.css is stale. Start npm run watch and save the template again. Or the file you edited lives outside the theme folder and no @source line covers it, so Tailwind never sees the class. Add the folder with @source and run the build again.
Also, don’t build class names dynamically by concatenating strings in your templates. Tailwind scans files as plain text, so a class name that never appears in full won’t make it into the output. Always write the complete class name. I wrote more about that in You can’t generate classes dynamically in Tailwind.
If you prefer Hugo to drive the Tailwind build itself, Hugo has a built-in css.TailwindCSS function that runs the Tailwind CLI from the asset pipeline, so hugo server rebuilds the CSS and you don’t need the second terminal. It still needs @tailwindcss/cli installed with npm. The CLI approach above stays simpler when you want Hugo and Tailwind as two clear processes.
Want me to talk about your product? You can sponsor this site.