Type-safe Datastar attribute helpers for templ templates.
datastar-templ is a Go library that provides compile-time type safety for Datastar attributes in templ templates. It bridges the gap between Go's templ templating system and Datastar's hypermedia framework, enabling you to build reactive web applications with full IDE autocomplete and type checking.
- Type-Safe: Compile-time checks for Datastar attributes with full IDE support
- High Performance: Optimized with sync.Pool and precise capacity allocation (~200-300ns/op)
- Complete Coverage: 60+ DOM events, HTTP actions, signals, and modifiers
- templ Integration: Native templ.Attributes for seamless template usage
go get github.com/yacobolo/datastar-templTested with Datastar 1.0.0-RC.7. Get started with Datastar.
This project uses Task for development commands:
# Install Task (if not already installed)
brew install go-task/tap/go-task # macOS
# or: go install github.com/go-task/task/v3/cmd/task@latest
# Common commands
task test # Run all tests
task test:coverage # Show test coverage
task bench # Run performance benchmarks
task check # Format, vet, and test
task --list # See all available commandsSee Taskfile.yml for the complete list of available tasks.
Import the package (commonly aliased as ds):
import ds "github.com/yacobolo/datastar-templ"templ TodoApp() {
<div { ds.Signals(
ds.JSON("todos", []Todo{}),
ds.String("newTodo", ""),
ds.String("filter", ""),
)... }>
// Data binding
<input
type="text"
{ ds.Bind("newTodo")... }
placeholder="New todo"
/>
// Event handlers with modifiers + SSE actions
<button { ds.OnClick(
ds.Post("/todos"),
ds.ModDebounce,
ds.Ms(300),
)... }>
Add Todo
</button>
// Conditional rendering + merging attributes
<div { ds.Merge(
ds.Show("$todos.length > 0"),
ds.Class(ds.Pair("active", "$filter !== ''")),
)... }>
<span { ds.Text("$todos.length + ' items'")... }></span>
</div>
// Event handlers
<input
type="search"
{ ds.Bind("filter")... }
{ ds.OnInput(
ds.Get("/search?q=$filter"),
ds.ModDebounce,
ds.Ms(300),
)... }
/>
</div>
}datastar-templ provides type-safe helpers that eliminate runtime errors and provide clear API semantics:
Signal Helpers (for data transformation):
ds.Signals(
ds.Int("count", 0), // Converts int to string
ds.String("message", "Hello"), // Adds quotes for JavaScript
ds.Bool("isOpen", true), // Formats boolean
ds.Float("price", 19.99), // Formats float
ds.JSON("user", userData), // Marshals complex types
)Pair Helper (for expression bindings):
// Use ds.Pair() for all attribute bindings
ds.Class(
ds.Pair("hidden", "$isHidden"),
ds.Pair("font-bold", "$isBold"),
)
ds.Computed(
ds.Pair("total", "$price * $qty"),
)
ds.Attr(
ds.Pair("disabled", "$loading"),
ds.Pair("title", "$tooltip"),
)
ds.Style(
ds.Pair("color", "$textColor"),
ds.Pair("display", "$visible ? 'block' : 'none'"),
)
// Or use ds.P() shorthand for brevity
ds.Class(ds.P("btn-primary", "$isMain"))Why two different helpers?
- Signal helpers (
Int,String, etc.) transform Go values into JavaScript-compatible strings - Pair helper (
PairorP) simply pairs keys with expressions - no transformation needed
See the Go package documentation for the complete API reference including:
- Signal Helpers: Int(), String(), Bool(), Float(), JSON() for type-safe data transformation
- Pair Helper: Pair() (or P()) for unified key-value expression bindings
- 60+ Event Handlers: OnClick, OnInput, OnSubmit, OnKeyDown, etc.
- HTTP Actions: Get, Post, Put, Patch, Delete with options
- Signal Management: Signals, Computed, Bind, SignalKey
- DOM Helpers: Text, Show, Class, Style, Attr
- Modifiers: Debounce, Throttle, Once, Passive, Capture, etc.
- Watchers: OnIntersect, OnInterval, OnSignalPatch
- Utilities: Merge, Ref, Indicator, Init, Effect
The library is highly optimized using:
- sync.Pool for builder reuse across requests
- Precise capacity allocation to avoid buffer reallocation
- Direct string building instead of JSON marshaling for primitives
Benchmark results (Apple M2):
BenchmarkSignals/simple-8 203.0 ns/op 392 B/op 5 allocs/op
BenchmarkClass/single-8 143.0 ns/op 376 B/op 4 allocs/op
BenchmarkComputed/single-8 170.2 ns/op 384 B/op 4 allocs/op
The implementation is only ~1.7x slower than raw inline fmt.Sprintf, while providing:
- ✅ Type safety at compile time
- ✅ Consistent API across all attributes
- ✅ Better maintainability
- ✅ No runtime reflection
Run tests:
go test ./...Contributions are welcome! Please feel free to submit a Pull Request.
MIT License - see LICENSE file for details
