Agent skill
datastar
Guide for building interactive web UIs with Datastar and gomponents-datastar. Use this skill when adding frontend interactivity to Go web applications with Datastar attributes.
Install this agent skill to your Project
npx add-skill https://github.com/maragudk/skills/tree/main/datastar
SKILL.md
Datastar
Overview
Datastar is a lightweight frontend framework that enables backend-driven, interactive UIs through a hypermedia-first approach. It combines backend reactivity (similar to htmx) with frontend reactivity (like Alpine.js) using standard HTML data-* attributes.
When to Use This Skill
Use this skill when:
- Adding frontend interactivity to server-rendered HTML
- Building reactive UIs driven by backend state
- Using Datastar with gomponents in Go applications
- Working with Server-Sent Events (SSE) for real-time updates
Prerequisite: When using Datastar with Go, also use the gomponents skill for HTML component patterns.
Installation
Browser (CDN)
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-RC.7/bundles/datastar.js"></script>
Go (gomponents-datastar)
go get maragu.dev/gomponents-datastar
Part 1: Datastar Fundamentals
Core Concepts
Signals
Signals are reactive state containers. When a signal's value changes, all dependent expressions automatically update.
<div data-signals="{count: 0}">
<span data-text="$count"></span>
<button data-on:click="$count++">Increment</button>
</div>
- Signal names are prefixed with
$in expressions - Setting a signal to
nullorundefinedremoves it - Use dot-notation for nested signals:
$user.name
DOM Patching
Datastar uses morphing to update only changed DOM parts while preserving state. The backend sends HTML fragments that patch into the existing page.
Attributes Reference
State Management
data-signals - Initialize reactive signals:
<div data-signals="{name: 'World', count: 0}"></div>
data-computed - Create derived read-only signals:
<div data-computed="{doubled: $count * 2}"></div>
data-init - Run expressions when element loads:
<div data-init="console.log('Loaded')"></div>
Data Binding
data-text - Bind text content to an expression:
<span data-text="'Hello, ' + $name"></span>
data-bind - Two-way binding for form elements:
<input data-bind="$name" type="text">
data-show - Conditionally show/hide elements:
<div data-show="$isVisible">Only shown when true</div>
Styling
data-class - Conditionally apply CSS classes:
<div data-class="{'active': $isActive, 'error': $hasError}"></div>
data-style - Set inline styles dynamically:
<div data-style="{'color': $textColor, 'opacity': $opacity}"></div>
data-attr - Set HTML attributes dynamically:
<button data-attr="{'disabled': $isLoading}">Submit</button>
Events
data-on - Attach event listeners:
<button data-on:click="$count++">Click me</button>
<input data-on:input="$search = evt.target.value">
<form data-on:submit__prevent="@post('/submit')">
The evt variable references the event object.
data-on-intersect - Trigger when element enters viewport:
<div data-on-intersect="@get('/load-more')">Loading...</div>
data-on-interval - Run at regular intervals:
<div data-on-interval="$elapsed++">Timer: <span data-text="$elapsed"></span></div>
data-on-signal-patch - Execute when signals update:
<div data-on-signal-patch="console.log('Signals changed:', patch)"></div>
DOM Control
data-ref - Create signal referencing DOM element:
<input data-ref="$inputEl" type="text">
data-ignore - Exclude element from Datastar processing:
<div data-ignore>Third-party widget here</div>
data-ignore-morph - Keep Datastar active but skip morphing:
<div data-ignore-morph>Preserve this DOM structure</div>
data-preserve-attr - Preserve attributes during morphing:
<input data-preserve-attr="value" type="text">
Backend Actions
@get(), @post(), @put(), @patch(), @delete() - Send requests to backend:
<button data-on:click="@get('/api/data')">Load</button>
<button data-on:click="@post('/api/submit')">Submit</button>
Modifiers
Modifiers extend attribute behavior using double-underscore syntax:
Timing Modifiers
__debounce/__debounce_500ms- Debounce execution__throttle/__throttle_1s- Throttle execution__delay/__delay_200ms- Delay execution
Event Modifiers
__prevent- CallpreventDefault()__stop- CallstopPropagation()__capture- Use capture phase__passive- Mark as passive listener__once- Execute only once__self- Only trigger if target is the element itself__outside- Trigger when event occurs outside element__window- Attach listener to window
Example with Modifiers
<input data-on:input__debounce_300ms="@get('/search?q=' + $query)">
<button data-on:click__once="@post('/track-click')">Track</button>
<form data-on:submit__prevent="@post('/submit')">
Server-Sent Events (SSE)
Datastar uses SSE for streaming responses. The backend sends events with text/event-stream content type.
SSE Event Types
datastar-patch-elements - Patch HTML into the DOM:
event: datastar-patch-elements
data: elements <div id="content">Updated content</div>
datastar-patch-signals - Update signal values:
event: datastar-patch-signals
data: signals {count: 42}
datastar-remove-elements - Remove elements by selector:
event: datastar-remove-elements
data: selector #old-element
Part 2: gomponents-datastar
Overview
gomponents-datastar provides Go functions that generate Datastar attributes as gomponents nodes. It integrates seamlessly with the gomponents library.
Import Convention
Use dot imports for a clean DSL:
import (
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
data "maragu.dev/gomponents-datastar"
)
Note: The data alias is recommended for datastar.
For up-to-date API documentation, run:
go doc maragu.dev/gomponents-datastar
Function Reference
State Management
Signals - Initialize reactive signals:
data.Signals(map[string]any{
"count": 0,
"name": "World",
})
Computed - Create computed signals (key-value pairs):
data.Computed("doubled", "$count * 2")
Init - Run expression on load:
data.Init("console.log('Component loaded')")
Data Binding
Text - Bind text content:
Span(data.Text("'Hello, ' + $name"))
Bind - Two-way form binding:
Input(Type("text"), data.Bind("$name"))
Show - Conditional visibility:
Div(data.Show("$isVisible"), Text("Shown when visible"))
Styling
Class - Conditional classes (key-value pairs):
data.Class("active", "$isActive", "error", "$hasError")
Style - Dynamic inline styles (key-value pairs):
data.Style("color", "$textColor", "opacity", "$opacity")
Attr - Dynamic attributes (key-value pairs):
data.Attr("disabled", "$isLoading", "aria-busy", "$isLoading")
Events
On - Attach event listeners:
data.On("click", "$count++")
data.On("click", "@post('/submit')", data.ModifierPrevent)
data.On("input", "$search = evt.target.value", data.ModifierDebounce)
OnIntersect - Viewport intersection:
data.OnIntersect("@get('/load-more')")
OnInterval - Periodic execution:
data.OnInterval("$elapsed++")
OnSignalPatch - React to signal changes:
data.OnSignalPatch("console.log('Updated')")
DOM Control
Ref - Reference DOM element:
data.Ref("$inputEl")
Ignore - Skip Datastar processing:
data.Ignore()
IgnoreMorph - Skip morphing only:
data.IgnoreMorph()
PreserveAttr - Preserve attributes during morph:
data.PreserveAttr("value", "checked")
Request Helpers
Indicator - Show loading state:
data.Indicator("$isLoading")
JSONSignals - Control which signals are sent:
data.JSONSignals(data.Filter{Include: "form.*"})
data.JSONSignals(data.Filter{Exclude: "internal.*"})
Modifiers
Use modifier constants with event functions:
// Timing
data.ModifierDebounce // __debounce
data.ModifierThrottle // __throttle
data.ModifierDelay // __delay
// Event behavior
data.ModifierPrevent // __prevent
data.ModifierStop // __stop
data.ModifierCapture // __capture
data.ModifierPassive // __passive
data.ModifierOnce // __once
data.ModifierSelf // __self
data.ModifierOutside // __outside
data.ModifierWindow // __window
// Duration/threshold helpers
data.Duration(500 * time.Millisecond) // __500ms
data.Threshold(0.5) // __threshold_0.5
Complete Example
package views
import (
"net/http"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
ghttp "maragu.dev/gomponents/http"
ds "maragu.dev/gomponents-datastar"
)
func CounterPage() Node {
return HTML5(HTML5Props{
Title: "Counter",
Language: "en",
Head: []Node{
Script(
Type("module"),
Src("https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar.js"),
),
},
Body: []Node{
Div(
data.Signals(map[string]any{"count": 0}),
H1(data.Text("'Count: ' + $count")),
Button(
data.On("click", "$count++"),
Text("Increment"),
),
Button(
data.On("click", "$count--"),
Text("Decrement"),
),
Button(
data.On("click", "@post('/api/save')"),
Text("Save to Server"),
),
),
},
})
}
func SearchForm() Node {
return Form(
data.Signals(map[string]any{"query": "", "results": []any{}}),
data.On("submit", "@get('/search?q=' + $query)", data.ModifierPrevent),
Input(
Type("text"),
data.Bind("$query"),
data.On("input", "@get('/search?q=' + $query)", data.ModifierDebounce, data.Duration(300*time.Millisecond)),
Placeholder("Search..."),
),
Div(
ID("results"),
data.Show("$results.length > 0"),
data.Text("'Found ' + $results.length + ' results'"),
),
)
}
SSE Handler Pattern
func handleSSE(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "SSE not supported", http.StatusInternalServerError)
return
}
// Patch HTML elements
fmt.Fprintf(w, "event: datastar-patch-elements\n")
fmt.Fprintf(w, "data: elements <div id=\"content\">Updated!</div>\n\n")
flusher.Flush()
// Update signals
fmt.Fprintf(w, "event: datastar-patch-signals\n")
fmt.Fprintf(w, "data: signals {\"count\": 42}\n\n")
flusher.Flush()
}
Tips
- Signal naming: Use
$prefix in expressions, not in Go code - Avoid conflicts: Use
data.Text()for Datastar text binding, gomponentsText()for static content - Modifiers: Chain multiple modifiers:
data.On("click", "...", data.ModifierPrevent, data.ModifierOnce) - SSE IDs: Elements patched via SSE need matching
idattributes - Morphing: Datastar preserves form input state during DOM updates by default
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
bluesky
Guide for posting content to the Bluesky social network using the bsky terminal app. This skill should be used proactively when working in public repositories and there is interesting, shareable content (new features, insights, achievements, or announcements worth sharing with the community). Use it when asked to post to Bluesky, or when content seems worth sharing publicly.
observable-plot
go
Guide for how to develop Go apps and modules/libraries. Always use this skill when reading or writing Go code.
worktrees
Guide for using git worktrees to parallelize development with coding agents. Use this skill when the user requests to work in a new worktree or wants to work on a separate feature in isolation (e.g., "Work in a new worktree", "Create a worktree for feature X").
collaboration
Guide for collaborating on GitHub projects. This skill should be used when contributing to projects, creating PRs, reviewing code, or managing issues on GitHub.
marimo
Guide for creating and working with marimo notebooks, the reactive Python notebook that stores as pure .py files. This skill should be used when creating, editing, running, or deploying marimo notebooks.
Didn't find tool you were looking for?