Server-rendered Go components that ship real HTML — no JavaScript framework required. Built on templ, HTMX, and Tailwind CSS v4.
Documentation · Why templ-components · Quick Start · Component Catalog
No DaisyUI. No Node.js. No framework lock-in.
Part of the GOTH stack — pair with cqrs-htmx (HTTP → CQRS wiring, auth, HTMX response building) and go-cqrs-lite (event sourcing core) for a complete server-rendered Go web stack with zero framework lock-in.
121 server-rendered components. 64 typed string enums (63 with IsValid()). 102 SVG icons. Zero client-side framework.
templ-components follows HATEOAS — the server renders HTML, JavaScript enhances it rather than replacing it. Every component uses Tailwind CSS v4 utility classes with built-in dark mode, CSP nonce support, and ARIA accessibility.
| Feature | templ-components | templUI | goshipit |
|---|---|---|---|
| CSS approach | Tailwind v4 (CSS-first) | Tailwind + CSS vars | Tailwind + DaisyUI |
| JavaScript | HATEOAS (enhances HTML) | Alpine.js | DaisyUI JS |
| Requires Node.js | No | No | Yes |
| Components | 121 | 40+ | — |
| Typed props | 59 enums | — | — |
| Dark mode | Built-in (tested) | CSS custom properties | Via DaisyUI |
| CSP compliant | Yes (nonce on all scripts) | Yes | — |
| Container queries | 8 opt-in components + fluid typography (cqi) |
— | — |
| Visual regression | chromedp pixel tests | — | — |
| HTMX integration | Built-in package | — | — |
| Datastar support | Opt-in package | — | — |
| Standalone library | Yes | No | No |
1. Install
go get github.com/larsartmann/templ-componentsBuild flag required: this library uses
encoding/json/v2(viaerrorpage). SetGOEXPERIMENT=jsonv2when building until Go 1.27 ships it as stable:export GOEXPERIMENT=jsonv2Without it the build fails with:
build constraints exclude all Go files in .../encoding/json/v2
2. Build a page
package main
import (
"github.com/larsartmann/templ-components/layout"
"github.com/larsartmann/templ-components/display"
"github.com/larsartmann/templ-components/icons"
)
templ Dashboard() {
@layout.Base(layout.DefaultPageProps()) {
@layout.ThemeScript("")
@display.PageHeader(display.PageHeaderProps{Title: "Dashboard"})
@display.Grid(display.GridProps{Cols: display.GridCols3}) {
@display.StatCard(display.StatCardProps{
Label: "Revenue", Value: "$42,189", Trend: display.TrendUp,
})
@display.StatCard(display.StatCardProps{
Label: "Users", Value: "1,204", Icon: icons.Users,
})
}
}
}3. Generate and run
templ generate && go run .Full guide: Installation · Quick Start
Cards, tables (Table + DataTable), tabs, modals, badges, buttons, avatars, tooltips, accordions, dropdowns, stat cards, page headers, definition lists, responsive grid, carousel, sparklines, bar charts, external links, collapsible sections, heatmaps, native SVG charts (LineChart, AreaChart, PieChart/Donut), dual-transport kanban boards (drag-and-drop + keyboard moves, optimistic moves with a pending register + failure revert), eyebrows, terminal-style log scrollbacks, and more.
@display.Card(display.CardProps{Title: "Users", Subtitle: "Manage users"}) {
<p>Card content</p>
}
@display.Card(display.CardProps{
Title: "Users",
TitleClass: "text-indigo-600",
HeaderClass: "bg-gray-50 dark:bg-gray-900/50",
}) {
<p>Override title and header classes without replacing the whole header.</p>
}
@display.StatCard(display.StatCardProps{Label: "Users", Value: "1,204", Icon: icons.Users, Change: "12%", Trend: display.TrendUp})
@display.Grid(display.GridProps{Cols: display.GridCols3, Gap: display.GridGapLG}) {
for _, u := range users {
@display.Card(display.CardProps{Title: u.Name}) { <p>{ u.Email }</p> }
}
}
@display.Table(display.TableProps{
Headers: []string{"Name", "Email", "Role"},
Rows: []display.TableRow{
display.SimpleTableRow("Alice", "alice@example.com", "Admin"),
},
Striped: true,
})
@display.Modal(display.ModalProps{Title: "Confirm", Size: display.ModalSizeSM}) {
<p>Are you sure?</p>
}
@display.Eyebrow(display.EyebrowProps{Text: "Deploy #142 · production"})
@display.Scrollback(display.ScrollbackProps{
Stagger: true,
Lines: []display.ScrollbackLine{
{Timestamp: "12:47:03.184", Tag: "query", Text: "ads.example.com A", Tone: display.ScrollbackToneInfo},
{Timestamp: "12:47:03.185", Tag: "action", Text: "NXDOMAIN", Tone: display.ScrollbackToneDanger},
},
})
@display.KanbanBoard(display.KanbanBoardProps{
Columns: []display.KanbanColumn{
{ID: "todo", Title: "To do", Tone: display.KanbanToneBlue,
Action: display.Button(display.ButtonProps{Text: "+ Add", Size: display.ButtonSizeSM,
Wire: &wire.Action{Method: wire.MethodPost, URL: "/api/kanban/add/todo"}}),
Cards: []display.KanbanCard{{ID: "c1", Title: "Write docs"}}},
{ID: "done", Title: "Done"},
},
Wire: &wire.Action{URL: "/api/kanban/move"},
})Alerts, toasts, spinners, progress bars, skeletons, step indicators, loading states.
@feedback.ToastContainer("")
@feedback.Toast(feedback.ToastProps{Message: "Saved!", Type: feedback.ToastSuccess})
@feedback.Alert(feedback.AlertProps{Title: "Warning", Type: feedback.AlertWarning})
@feedback.ProgressBar(feedback.ProgressBarProps{Current: 45, Total: 100})
@feedback.SkeletonCardGrid(feedback.SkeletonCardGridProps{Count: 6})Inputs, selects, textareas, checkboxes, radios, toggles, file inputs, date pickers, comboboxes, sliders, ratings, tags input, validation, debounced filter inputs.
Forms are dual-transport: FormProps.Wire submits the same form over
HTMX or Datastar (server-side validation round-trip included — see
docs/recipes/server-side-validation.md),
with debounced search (FilterInput) and multipart uploads
(docs/recipes/file-upload.md) working under
both runtimes.
@forms.Input(forms.InputProps{Name: "email", Type: forms.InputEmail, Label: "Email"})
@forms.Select(forms.SelectProps{Name: "country", Label: "Country",
Options: []forms.SelectOption{{Value: "de", Label: "Germany"}}})
@forms.Toggle(forms.ToggleProps{Name: "notifications", Label: "Enable notifications"})
@forms.Combobox(forms.ComboboxProps{Name: "country", Label: "Country",
Options: []forms.ComboboxOption{{Value: "de", Label: "Germany"}}})Nav bars, breadcrumbs, pagination, mobile menus, sidebar, load-more.
Page shells and layout primitives: Base/Minimal HTML documents, theme script/toggle, CSP-safe Script/Stylesheet, AppShell, Container, Split, Stack.
@navigation.SimpleNav(navigation.SimpleNavProps{BrandText: "MyApp", CurrentPath: "/"})
@navigation.Breadcrumbs(navigation.BreadcrumbsProps{Items: []navigation.BreadcrumbItem{
{Text: "Home", Href: "/"}, {Text: "Users", Active: true},
}})
@navigation.Pagination(navigation.PaginationProps{CurrentPage: 2, TotalPages: 10})
@navigation.SidebarNav(navigation.SidebarNavProps{CurrentPath: "/users"})Typed icon constants, no icon library dependency.
@icons.Icon(icons.Home, "h-5 w-5 text-gray-500")
@icons.Icon(icons.Check, "h-6 w-6 text-green-500")Loading indicators, error handling, CSRF protection, out-of-band swaps, View Transitions, polled regions.
@htmx.GlobalErrorHandling(htmx.DefaultErrorHandlingConfig())
@htmx.LoadingIndicator(feedback.Spinner(feedback.SpinnerMD, "text-blue-600"))
@htmx.ViewTransitions(htmx.ViewTransitionsProps{Global: true})Datastar runtime injection, SSE-powered live regions, and loading indicators. An opt-in complement to HTMX for real-time streaming apps — zero new Go dependencies.
@datastar.SDKScript(datastar.DefaultSDKScriptProps())
@datastar.LiveRegion(datastar.LiveRegionProps{URL: "/stream/metrics"}) {
@display.StatCard(display.StatCardProps{Label: "Active Users", Value: "—"})
}
@datastar.Indicator(datastar.IndicatorProps{Signal: "fetching"})See docs/recipes/datastar-integration.md for the HTMX-to-Datastar migration guide.
A transport-agnostic wiring contract: describe a hypermedia exchange once as a typed wire.Action, render it as htmx or Datastar attributes, and serve both from one endpoint with wire.Handler.
// Same Action shape, either dialect — one field switches the transport.
wire.Action{URL: "/api/items", Target: "#items"} // htmx (default)
wire.Action{Transport: wire.TransportDatastar, URL: "/api/items"} // datastar
// Whole-form submission is symmetric too — forms.Form wires both dialects,
// fields serialize natively (htmx) or via contentType:'form' (Datastar).
forms.FormProps{Wire: &wire.Action{Transport: wire.TransportDatastar, Method: wire.MethodPost, URL: "/api/save"}}
// One endpoint serves both: Datastar callers get response-header targeting,
// htmx and plain callers pass through.
mux.Handle("/api/items", wire.Handler(wire.PatchTarget{Selector: "#items"}, fragmentHandler))Components take it via BaseProps.Attrs (spread Attributes() anywhere) or a typed Wire field (display.Button, navigation.LoadMore, forms.Form). Zero-JS contract: attributes only, CSP-safe without a nonce. See docs/transport-wiring.md.
CSP-safe wrapper for Apache ECharts interactive charts (tooltips, zoom, 25+ chart types). Follows the same opt-in pattern as datastar — does NOT import go-echarts. Consumer builds charts with go-echarts and passes RenderSnippet() output.
@echarts.SDKScript(echarts.DefaultSDKScriptProps())
@echarts.EChart(echarts.EChartsProps{Element: snippet.Element, Script: snippet.Script, Nonce: nonce})See docs/recipes/echarts-adapter.md for the Tier 1 vs Tier 2 guide.
Structured error pages with family-aware styling, HTTP handler integration, dedicated 404.
@errorpage.NotFound404(errorpage.DefaultNotFound404Props())
// Full diagnostic page — status code, code, title, message, why, fix,
// context, cause chain, and action render as one card.
@errorpage.ErrorPage(errorpage.ErrorPageProps{
Family: errorpage.FamilyTransient,
StatusCode: 503,
Code: errorpage.CodeUnavailable,
Title: "Service temporarily unavailable",
Message: "We're performing maintenance or experiencing high traffic.",
Fix: "Wait a moment and refresh the page.",
WayOut: "Retry",
WayOutHref: "/",
})
// One-call handler integration with go-error-family.
mux.Handle("/api/thing", errorpage.ErrorHandler(err, errorpage.ErrorHandlerConfig{}))Type-safe. 64 typed string enums (63 with IsValid()) make invalid states unrepresentable. Props structs embed utils.BaseProps for consistent ID, class, attributes, ARIA label, and CSP nonce propagation.
Accessible. ARIA attributes, roles, keyboard navigation, and screen-reader text across all interactive components. Native <dialog> for modals, <details> for accordions, <search> landmark for search inputs.
CSP-ready. All inline scripts use nonce attributes. No eval(), no inline event handlers. Integration test suite verifies compliance on every component.
Dark mode. Every component has proper dark: variants — enforced by TestDarkModeCompliance + TestDarkModeSemanticColors regression tests. ThemeScript prevents FOUC.
Server-rendered. Zero client-side JavaScript by default. Interactive features use minimal vanilla JS with nonce-based CSP.
Pay for what you use. Import only the packages you need. No monolithic bundle.
Tested at two layers. HTML golden-file snapshots (utils/golden) catch
structure/class drift; pixel-level visual regression tests (visualtest/, a
separate Go module so chromedp never pollutes your dependency graph) render each
component in headless Chromium and diff pixels — catching layout shifts,
dark-mode color regressions, and RTL mirroring that string tests cannot. The
visual harness covers rest/hover/focus and open states (Dropdown/Popover/
ContextMenu via native Popover API). Run with nix run .#visual. See
docs/visual-testing.md.
Tailwind v4 uses CSS-first configuration. Vendor the dependency so Tailwind can scan the .templ source files:
go mod vendorThen in your CSS:
@import "tailwindcss";
@source "../vendor/github.com/larsartmann/templ-components";
@custom-variant dark (&:where(.dark, .dark *));tailwindcss -i app.css -o styles.css --minifyIf your project uses BuildFlow, the tailwind-build provider handles this automatically.
Components emit standard Tailwind classes (bg-blue-600, text-gray-900). Override colors without touching component code:
@theme {
--color-blue-600: #4f46e5;
--color-blue-500: #6366f1;
}For semantic tokens (bg-tc-primary, text-tc-danger), copy the included templ-components-theme.css.
See the Theming guide for details.
| Metric | Value |
|---|---|
| Components | 121 |
| SVG icons | 102 |
| Typed enums | 64 (63 with IsValid) |
| Packages | 15 |
| Tests | ~1,070 test functions + ~1,240 subtests |
| Visual goldens | 173 pixel-level regression tests (chromedp) |
| Dependencies | 3 (templ, tailwind-merge-go, go-error-family) |
The library is verified by a three-tier strategy that catches different classes of regression:
| Tier | What | Where | Catches |
|---|---|---|---|
| HTML golden | Snapshot the rendered HTML (CSS classes sorted, auto-IDs normalized) | utils/golden — 258 .golden files |
Structure, attribute, and class changes |
| Drift-guard scanners | Cross-cutting invariant tests | utils/ |
Dark-mode gaps, missing motion-reduce:, physical RTL props, CSP nonce regressions, lint-config drift, stale CSS, ordered-substring flake risk |
| Visual regression | Pixel-level PNG diff in headless Chromium | visualtest/ (separate module) |
Layout shifts, dark-mode color regressions, RTL mirroring |
nix run .#verify # generate + build + test + lint — the "done" check
nix run .#visual # pixel-level visual regression (needs Chromium; skips if absent)See docs/testing-guide.md for the full strategy, how to update goldens, and how to add coverage for a new component.
- Go 1.26+ (
GOEXPERIMENT=jsonv2) - templ CLI (install)
- Tailwind CSS 4.x+
- HTMX 2.x (optional, for
htmxpackage)
Contributing? The repo ships a committed
.envrcfor direnv that exportsGOEXPERIMENT=jsonv2andGOWORK=offfor every tool (go, gopls, IDE) — not just insidenix develop. Rundirenv allowonce after cloning. It is tracked (no secrets) and guarded byTestEnvrcConsistency. If you skip direnv, set those two env vars manually before building.
This library is part of the GOTH stack (Go + Templ + HTMX):
| Project | What it does |
|---|---|
| cqrs-htmx | Production CQRS+ES framework with WebAuthn, RBAC, multi-tenancy, SSE. |
| go-cqrs-lite | Minimal CQRS/ES building blocks. |
| go-error-family | Structured error families. Used by templ-components' errorpage package. |
Contributions are welcome. After cloning, activate the tracked pre-commit guards once:
scripts/setup-hooks.shSee CONTRIBUTING.md for setup, conventions, and workflow.