in expressions\n- Setting a signal to `null` or `undefined` removes it\n- Use dot-notation for nested signals: `$user.name`\n\n### DOM Patching\n\nDatastar uses morphing to update only changed DOM parts while preserving state. The backend sends HTML fragments that patch into the existing page.\n\n## Attributes Reference\n\n### State Management\n\n**data-signals** - Initialize reactive signals:\n```html\n\u003cdiv data-signals=\"{name: 'World', count: 0}\">\u003c/div>\n```\n\n**data-computed** - Create derived read-only signals:\n```html\n\u003cdiv data-computed=\"{doubled: $count * 2}\">\u003c/div>\n```\n\n**data-init** - Run expressions when element loads:\n```html\n\u003cdiv data-init=\"console.log('Loaded')\">\u003c/div>\n```\n\n### Data Binding\n\n**data-text** - Bind text content to an expression:\n```html\n\u003cspan data-text=\"'Hello, ' + $name\">\u003c/span>\n```\n\n**data-bind** - Two-way binding for form elements:\n```html\n\u003cinput data-bind=\"$name\" type=\"text\">\n```\n\n**data-show** - Conditionally show/hide elements:\n```html\n\u003cdiv data-show=\"$isVisible\">Only shown when true\u003c/div>\n```\n\n### Styling\n\n**data-class** - Conditionally apply CSS classes:\n```html\n\u003cdiv data-class=\"{'active': $isActive, 'error': $hasError}\">\u003c/div>\n```\n\n**data-style** - Set inline styles dynamically:\n```html\n\u003cdiv data-style=\"{'color': $textColor, 'opacity': $opacity}\">\u003c/div>\n```\n\n**data-attr** - Set HTML attributes dynamically:\n```html\n\u003cbutton data-attr=\"{'disabled': $isLoading}\">Submit\u003c/button>\n```\n\n### Events\n\n**data-on** - Attach event listeners:\n```html\n\u003cbutton data-on:click=\"$count++\">Click me\u003c/button>\n\u003cinput data-on:input=\"$search = evt.target.value\">\n\u003cform data-on:submit__prevent=\"@post('/submit')\">\n```\n\nThe `evt` variable references the event object.\n\n**data-on-intersect** - Trigger when element enters viewport:\n```html\n\u003cdiv data-on-intersect=\"@get('/load-more')\">Loading...\u003c/div>\n```\n\n**data-on-interval** - Run at regular intervals:\n```html\n\u003cdiv data-on-interval=\"$elapsed++\">Timer: \u003cspan data-text=\"$elapsed\">\u003c/span>\u003c/div>\n```\n\n**data-on-signal-patch** - Execute when signals update:\n```html\n\u003cdiv data-on-signal-patch=\"console.log('Signals changed:', patch)\">\u003c/div>\n```\n\n### DOM Control\n\n**data-ref** - Create signal referencing DOM element:\n```html\n\u003cinput data-ref=\"$inputEl\" type=\"text\">\n```\n\n**data-ignore** - Exclude element from Datastar processing:\n```html\n\u003cdiv data-ignore>Third-party widget here\u003c/div>\n```\n\n**data-ignore-morph** - Keep Datastar active but skip morphing:\n```html\n\u003cdiv data-ignore-morph>Preserve this DOM structure\u003c/div>\n```\n\n**data-preserve-attr** - Preserve attributes during morphing:\n```html\n\u003cinput data-preserve-attr=\"value\" type=\"text\">\n```\n\n### Backend Actions\n\n**@get(), @post(), @put(), @patch(), @delete()** - Send requests to backend:\n```html\n\u003cbutton data-on:click=\"@get('/api/data')\">Load\u003c/button>\n\u003cbutton data-on:click=\"@post('/api/submit')\">Submit\u003c/button>\n```\n\n## Modifiers\n\nModifiers extend attribute behavior using double-underscore syntax:\n\n### Timing Modifiers\n- `__debounce` / `__debounce_500ms` - Debounce execution\n- `__throttle` / `__throttle_1s` - Throttle execution\n- `__delay` / `__delay_200ms` - Delay execution\n\n### Event Modifiers\n- `__prevent` - Call `preventDefault()`\n- `__stop` - Call `stopPropagation()`\n- `__capture` - Use capture phase\n- `__passive` - Mark as passive listener\n- `__once` - Execute only once\n- `__self` - Only trigger if target is the element itself\n- `__outside` - Trigger when event occurs outside element\n- `__window` - Attach listener to window\n\n### Example with Modifiers\n```html\n\u003cinput data-on:input__debounce_300ms=\"@get('/search?q=' + $query)\">\n\u003cbutton data-on:click__once=\"@post('/track-click')\">Track\u003c/button>\n\u003cform data-on:submit__prevent=\"@post('/submit')\">\n```\n\n## Server-Sent Events (SSE)\n\nDatastar uses SSE for streaming responses. The backend sends events with `text/event-stream` content type.\n\n### SSE Event Types\n\n**datastar-patch-elements** - Patch HTML into the DOM:\n```\nevent: datastar-patch-elements\ndata: elements \u003cdiv id=\"content\">Updated content\u003c/div>\n```\n\n**datastar-patch-signals** - Update signal values:\n```\nevent: datastar-patch-signals\ndata: signals {count: 42}\n```\n\n**datastar-remove-elements** - Remove elements by selector:\n```\nevent: datastar-remove-elements\ndata: selector #old-element\n```\n\n---\n\n# Part 2: gomponents-datastar\n\n## Overview\n\ngomponents-datastar provides Go functions that generate Datastar attributes as gomponents nodes. It integrates seamlessly with the gomponents library.\n\n## Import Convention\n\nUse dot imports for a clean DSL:\n\n```go\nimport (\n . \"maragu.dev/gomponents\"\n . \"maragu.dev/gomponents/html\"\n data \"maragu.dev/gomponents-datastar\"\n)\n```\n\nNote: The `data` alias is recommended for datastar.\n\nFor up-to-date API documentation, run:\n```\ngo doc maragu.dev/gomponents-datastar\n```\n\n## Function Reference\n\n### State Management\n\n**Signals** - Initialize reactive signals:\n```go\ndata.Signals(map[string]any{\n \"count\": 0,\n \"name\": \"World\",\n})\n```\n\n**Computed** - Create computed signals (key-value pairs):\n```go\ndata.Computed(\"doubled\", \"$count * 2\")\n```\n\n**Init** - Run expression on load:\n```go\ndata.Init(\"console.log('Component loaded')\")\n```\n\n### Data Binding\n\n**Text** - Bind text content:\n```go\nSpan(data.Text(\"'Hello, ' + $name\"))\n```\n\n**Bind** - Two-way form binding:\n```go\nInput(Type(\"text\"), data.Bind(\"$name\"))\n```\n\n**Show** - Conditional visibility:\n```go\nDiv(data.Show(\"$isVisible\"), Text(\"Shown when visible\"))\n```\n\n### Styling\n\n**Class** - Conditional classes (key-value pairs):\n```go\ndata.Class(\"active\", \"$isActive\", \"error\", \"$hasError\")\n```\n\n**Style** - Dynamic inline styles (key-value pairs):\n```go\ndata.Style(\"color\", \"$textColor\", \"opacity\", \"$opacity\")\n```\n\n**Attr** - Dynamic attributes (key-value pairs):\n```go\ndata.Attr(\"disabled\", \"$isLoading\", \"aria-busy\", \"$isLoading\")\n```\n\n### Events\n\n**On** - Attach event listeners:\n```go\ndata.On(\"click\", \"$count++\")\ndata.On(\"click\", \"@post('/submit')\", data.ModifierPrevent)\ndata.On(\"input\", \"$search = evt.target.value\", data.ModifierDebounce)\n```\n\n**OnIntersect** - Viewport intersection:\n```go\ndata.OnIntersect(\"@get('/load-more')\")\n```\n\n**OnInterval** - Periodic execution:\n```go\ndata.OnInterval(\"$elapsed++\")\n```\n\n**OnSignalPatch** - React to signal changes:\n```go\ndata.OnSignalPatch(\"console.log('Updated')\")\n```\n\n### DOM Control\n\n**Ref** - Reference DOM element:\n```go\ndata.Ref(\"$inputEl\")\n```\n\n**Ignore** - Skip Datastar processing:\n```go\ndata.Ignore()\n```\n\n**IgnoreMorph** - Skip morphing only:\n```go\ndata.IgnoreMorph()\n```\n\n**PreserveAttr** - Preserve attributes during morph:\n```go\ndata.PreserveAttr(\"value\", \"checked\")\n```\n\n### Request Helpers\n\n**Indicator** - Show loading state:\n```go\ndata.Indicator(\"$isLoading\")\n```\n\n**JSONSignals** - Control which signals are sent:\n```go\ndata.JSONSignals(data.Filter{Include: \"form.*\"})\ndata.JSONSignals(data.Filter{Exclude: \"internal.*\"})\n```\n\n## Modifiers\n\nUse modifier constants with event functions:\n\n```go\n// Timing\ndata.ModifierDebounce // __debounce\ndata.ModifierThrottle // __throttle\ndata.ModifierDelay // __delay\n\n// Event behavior\ndata.ModifierPrevent // __prevent\ndata.ModifierStop // __stop\ndata.ModifierCapture // __capture\ndata.ModifierPassive // __passive\ndata.ModifierOnce // __once\ndata.ModifierSelf // __self\ndata.ModifierOutside // __outside\ndata.ModifierWindow // __window\n\n// Duration/threshold helpers\ndata.Duration(500 * time.Millisecond) // __500ms\ndata.Threshold(0.5) // __threshold_0.5\n```\n\n## Complete Example\n\n```go\npackage views\n\nimport (\n \"net/http\"\n\n . \"maragu.dev/gomponents\"\n . \"maragu.dev/gomponents/html\"\n ghttp \"maragu.dev/gomponents/http\"\n ds \"maragu.dev/gomponents-datastar\"\n)\n\nfunc CounterPage() Node {\n return HTML5(HTML5Props{\n Title: \"Counter\",\n Language: \"en\",\n Head: []Node{\n Script(\n Type(\"module\"),\n Src(\"https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar.js\"),\n ),\n },\n Body: []Node{\n Div(\n data.Signals(map[string]any{\"count\": 0}),\n\n H1(data.Text(\"'Count: ' + $count\")),\n\n Button(\n data.On(\"click\", \"$count++\"),\n Text(\"Increment\"),\n ),\n\n Button(\n data.On(\"click\", \"$count--\"),\n Text(\"Decrement\"),\n ),\n\n Button(\n data.On(\"click\", \"@post('/api/save')\"),\n Text(\"Save to Server\"),\n ),\n ),\n },\n })\n}\n\nfunc SearchForm() Node {\n return Form(\n data.Signals(map[string]any{\"query\": \"\", \"results\": []any{}}),\n data.On(\"submit\", \"@get('/search?q=' + $query)\", data.ModifierPrevent),\n\n Input(\n Type(\"text\"),\n data.Bind(\"$query\"),\n data.On(\"input\", \"@get('/search?q=' + $query)\", data.ModifierDebounce, data.Duration(300*time.Millisecond)),\n Placeholder(\"Search...\"),\n ),\n\n Div(\n ID(\"results\"),\n data.Show(\"$results.length > 0\"),\n data.Text(\"'Found ' + $results.length + ' results'\"),\n ),\n )\n}\n```\n\n## SSE Handler Pattern\n\n```go\nfunc handleSSE(w http.ResponseWriter, r *http.Request) {\n w.Header().Set(\"Content-Type\", \"text/event-stream\")\n w.Header().Set(\"Cache-Control\", \"no-cache\")\n w.Header().Set(\"Connection\", \"keep-alive\")\n\n flusher, ok := w.(http.Flusher)\n if !ok {\n http.Error(w, \"SSE not supported\", http.StatusInternalServerError)\n return\n }\n\n // Patch HTML elements\n fmt.Fprintf(w, \"event: datastar-patch-elements\\n\")\n fmt.Fprintf(w, \"data: elements \u003cdiv id=\\\"content\\\">Updated!\u003c/div>\\n\\n\")\n flusher.Flush()\n\n // Update signals\n fmt.Fprintf(w, \"event: datastar-patch-signals\\n\")\n fmt.Fprintf(w, \"data: signals {\\\"count\\\": 42}\\n\\n\")\n flusher.Flush()\n}\n```\n\n## Tips\n\n1. **Signal naming:** Use ` datastar · Skills-SI 市场 prefix in expressions, not in Go code\n2. **Avoid conflicts:** Use `data.Text()` for Datastar text binding, gomponents `Text()` for static content\n3. **Modifiers:** Chain multiple modifiers: `data.On(\"click\", \"...\", data.ModifierPrevent, data.ModifierOnce)`\n4. **SSE IDs:** Elements patched via SSE need matching `id` attributes\n5. **Morphing:** Datastar preserves form input state during DOM updates by default\n","licenseDocuments":[]}}
界面已本地化;技能介绍与说明文档按所选语言由 AI 自动翻译,代码、命令、路径和专业术语保留原文,以原作者文档为准。
探索技能
设计与创意 · SKILL.md

datastar

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.

独立 GitHub 快照 · 自动分类 · 以原作者文档为准

技能说明

GitHub

name: datastar description: 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. license: MIT

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 null or undefined removes 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 - Call preventDefault()
  • __stop - Call stopPropagation()
  • __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

  1. Signal naming: Use $ prefix in expressions, not in Go code
  2. Avoid conflicts: Use data.Text() for Datastar text binding, gomponents Text() for static content
  3. Modifiers: Chain multiple modifiers: data.On("click", "...", data.ModifierPrevent, data.ModifierOnce)
  4. SSE IDs: Elements patched via SSE need matching id attributes
  5. Morphing: Datastar preserves form input state during DOM updates by default
我的收藏