Skip to content
Customizing the UI

Customizing the UI

The whole UI — templates, compiled CSS/JS, icons — ships inside your binary via go:embed. Two overlay filesystems on Config let you replace any piece without forking:

steward.New(steward.Config{
    // ...
    Brand:       "Acme Ops",
    TemplatesFS: os.DirFS("admin/templates"), // your files win over embedded ones
    AssetsFS:    os.DirFS("admin/assets"),    // extra icons, overridden CSS, …
    Dev:         true,                        // re-parse templates per request
})

Overriding templates

Copy the built-in views into your project and edit:

steward publish views --dir admin/templates

Template names are full relative paths (grid/table.html, layout/sidebar.html, auth/login.html, …). A file at the same path in TemplatesFS replaces the embedded one — override a single partial or the whole layout. With Dev: true changes apply on refresh; in production templates parse once at build.

Useful template funcs available everywhere: {{icon "name"}} (inline SVG), {{url "posts" "create"}} (prefix-aware URLs), {{asset "dist/app.css"}} (content-hashed asset paths), and {{dict}} for passing values into partials.

Icons

Icons are inline SVGs resolved as icons/<name>.svg through the assets overlay — a curated Lucide subset is embedded. To add one, drop the SVG into your AssetsFS under icons/ and reference it:

steward.Register[Invoice](app).Icon("receipt") // AssetsFS: icons/receipt.svg

Branding & chrome

  • Config.Brand names the panel in the sidebar and titles.
  • Config.Prefix moves the whole panel (/admin by default).
  • Dark mode is built in — the toggle stores a cookie and the server renders the right theme with no flash of the wrong mode.

Custom pages

Resources can mount extra routes next to their CRUD ones, rendered with your own templates:

posts.Page("GET", "stats", func(c *steward.Context) error {
    return c.Render("pages/post_stats.html", "Post stats", data)
})

The template lives in your TemplatesFS (here at pages/post_stats.html) and renders inside the admin layout; the handler can also return c.JSON(...), c.Envelope(...), or c.Redirect(...).

Rebuilding the frontend bundle

You rarely need this — the compiled bundle is committed and embedded. If you fork the styles themselves: make tailwind-bin downloads the Tailwind standalone binary once, then make assets (esbuild via its Go API + that binary) rebuilds assets/dist. No Node at any point.

no_ui builds

go build -tags no_ui compiles the entire UI out — templates, assets, and handlers replaced by stubs — for API-only deployments of an app that embeds Steward.