Tailwind CSS¶
Burrow projects style their HTML with Tailwind CSS via the standalone Rust CLI — no npm, no PostCSS, no plugins. The CLI is pinned and installed via mise alongside the Go toolchain.
Dark mode follows the browser's prefers-color-scheme setting; no user-toggleable theme machinery is shipped by Burrow. If you want a toggle, write it yourself.
Coming from cmd/burrow-tailwind?
The standalone cmd/burrow-tailwind tool was removed in v0.22 (after a one-cycle deprecation shim in v0.21). Replace the tool directive in go.mod and the invocations in .mise.toml / .air.toml:
What you ship¶
A realistic Burrow project keeps the binary in cmd/server/ and every app — including the project shell that owns the shared layout — under internal/. There is no project-root package:
myapp/
├── go.mod
├── .mise.toml # pins Go + tailwindcss
├── .gitignore # /.tailwind/, bin/
├── tailwind.css # @import tailwindcss + @import sources.css
│
├── cmd/server/
│ └── main.go # server config + app registration
│
└── internal/
├── app/ # shell app: layout + project static assets
│ ├── app.go # HasTemplates + HasStaticFiles
│ ├── templates/app/layout.html
│ └── static/app.min.css # built CSS, embedded
├── pages/ # small app
│ ├── app.go
│ └── templates/pages/home.html
└── notes/ # CRUD app split by purpose
├── app.go, handlers.go, models.go, repository.go
└── templates/notes/list.html
A flat single-file project (one main.go, one templates/, one static/ at the root) is also fine for demos and prototypes. Both layouts use the same tailwind.css and the same toolchain — burrow tailwind auto-discovers either one (see below).
Why the shell is in internal/app/¶
Go's //go:embed directive can only descend from the file containing it — ../../templates is not allowed. Two consequences flow from that constraint:
- Every embedded asset lives inside the package that owns it. Project-root
templates/andstatic/directories would either need a root-level package (an extra import, an extra file) or have to move into some package's subtree. Putting them insideinternal/app/keeps the project root flat and treats the shell as a regular Burrow app. main.gostays thin. No//go:embeddirectives, no asset wiring. Juststaticfiles.New(emptyFS)for the framework's static root, thensrv := burrow.NewServer(staticApp, htmx.New(), app.New(), pages.New(), notes.New()).
The shell's app.go:
package app
import (
"embed"
"io/fs"
)
//go:embed templates
var templateFS embed.FS
//go:embed static
var staticFS embed.FS
func New() *App { return &App{} }
type App struct{}
func (a *App) Name() string { return "app" }
func (a *App) TemplateFS() fs.FS {
sub, _ := fs.Sub(templateFS, "templates")
return sub
}
// StaticFS serves the embedded `static/` under the URL prefix `/static/app/`.
// `fs.Sub` strips the `static/` directory from the embedded paths so the
// actual file resolves at `app.min.css`, served as `/static/app/<hash>.app.min.css`.
func (a *App) StaticFS() (string, fs.FS) {
sub, _ := fs.Sub(staticFS, "static")
return "app", sub
}
The layout template then references the CSS as:
The app/ prefix mirrors how every Burrow contrib's assets are namespaced (htmx serves at /static/htmx/..., admin at /static/admin/..., etc.).
Quick start¶
Or: scaffold with burrow new
The six steps below produce — by hand — the same layout that ships pre-wired in burrow new: cmd/<name>/, internal/app/ shell, .mise.toml with the toolchain pinned, .air.toml integration, a buildable tailwind.css. For a fresh project, run go install github.com/oliverandrich/burrow/cmd/burrow@latest && burrow new myapp --module github.com/you/myapp; see Tooling for all flags. Reading the steps below is still worthwhile to understand what the scaffold wires up.
1. Pin tooling with mise¶
2. Add burrow as a Go tool¶
3. Create your tailwind.css¶
Two lines. The ./.tailwind/sources.css import is regenerated on every burrow tailwind invocation; it carries @source directives for both Burrow's contribs and your project's own templates. You don't list contribs or internal apps manually.
4. Add .tailwind/ and the build output to .gitignore¶
Whether you also gitignore internal/app/static/app.min.css depends on your deployment style (see Production build below).
5. Build CSS¶
6. Link it from your layout¶
What burrow tailwind does¶
burrow tailwind is a thin sub-command that wraps tailwindcss. On every invocation it writes .tailwind/sources.css with @source "<absolute path>"; lines for:
- Every
<burrow>/contrib/<app>/templates/directory (resolved viago list -m). - The project's
./templates/if it exists (flat layout). - Every
./internal/<app>/templates/directory (project's structured layout).
Then it forwards every argument verbatim to tailwindcss. New contribs in future Burrow releases — or new internal apps in your own project — get picked up automatically on the next invocation. No per-project source-list maintenance.
The two examples in this repo show both layouts in action: example/hello is the flat single-file app; example/notes is the realistic cmd/server + internal/<app>/ layout with a Den-backed CRUD use case.
Customizing contrib styles¶
Tailwind utility classes are baked into each contrib's templates (e.g. contrib/admin/templates/dashboard.html uses class="rounded-lg bg-white shadow"). To change how a contrib looks:
-
Color tweaks via theme tokens — your
tailwind.csscan override Tailwind's design tokens with@theme:@import "tailwindcss"; @import "./.tailwind/sources.css"; @theme { --color-blue-500: oklch(0.65 0.18 250); /* your brand blue */ --font-sans: "Inter", system-ui, sans-serif; }Every contrib using
bg-blue-500,text-blue-500etc. picks up the new value. -
Structural overrides — redefine the contrib's template name in your own
internal/<app>/templates/directory. Burrow'shtml/templateuses last-define-wins, but registration order matters: your override-app must be registered after the contrib it overrides, either by listing it later inburrow.NewServer(...)or by declaring the contrib as aDependencies()of your app. See Layouts → Overriding Contrib Templates for the full pattern with worked examples.
Development workflow with burrow dev¶
burrow dev watches the project, rebuilds CSS with burrow tailwind, and restarts the Go app on every change — sequentially, in one process.
# .mise.toml
[tools]
go = "1.26"
"github:tailwindlabs/tailwindcss" = "4"
[tasks.dev]
description = "Run app with hot-reload (burrow dev)"
run = "go tool burrow dev"
On any .go, .html, .css, .toml, .yml, or .yaml edit inside the project:
burrow devSIGTERMs the running app.- It re-runs
burrow tailwindonce (no--watch), regeneratinginternal/app/static/app.min.css. - It re-runs
go run. Go rebuilds the binary, which re-embeds the freshly-written CSS via//go:embed, then starts.
The Tailwind step is sequential, not parallel: a long-running tailwindcss --watch would write the CSS to disk while the running Go binary still serves the previously-embedded copy — //go:embed is a compile-time directive. Only a Go rebuild picks up new CSS, so there's no point running Tailwind in --watch mode alongside the app. Go's build cache keeps warm restarts fast.
Auto-discovery resolves the entry-point (single cmd/<name>/main.go) and the Tailwind paths (tailwind.css plus the single internal/<app>/static/). Override with --app, --css-in, --css-out, or disable Tailwind via --no-tailwind.
Note:
.tailwind/sources.cssis regenerated on everyburrow tailwindrun. A new Burrow contrib that lands viago get -u burrowis picked up on the next file-change rebuild — same for new internal apps you add.
Production build¶
The output is minified. Combined with contrib/staticfiles content-hashing and Cache-Control: immutable, the CSS is downloaded once per release.
Whether to commit internal/app/static/app.min.css depends on your deployment style:
- Commit it — convenient for
go run ./cmd/serverto work without first invoking the build. Right call for example apps and demos. - Generate at CI/build time — leaner repo, no risk of stale committed CSS. Right call for production projects with a real build pipeline.
See also¶
- Tailwind CSS documentation
- contrib/staticfiles — content-hashing of CSS and other assets
- Starter
tailwind.css:docs/guide/tailwind.example.css