Claude Code Plugins

Community-maintained marketplace

Feedback

using-datastar

@aronasorman/dotfiles
0
0

Use when building hypermedia UIs with Datastar v1.0 - covers attribute syntax ($signals, @actions), SSE response format, Go SDK patterns, and common mistakes from HTMX/Alpine mental models

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name using-datastar
description Use when building hypermedia UIs with Datastar v1.0 - covers attribute syntax ($signals, @actions), SSE response format, Go SDK patterns, and common mistakes from HTMX/Alpine mental models

Using Datastar

Overview

Datastar is a hypermedia framework (10KB) combining frontend reactivity (like Alpine) with backend-driven updates (like HTMX) via Server-Sent Events. Key insight: the server pushes DOM updates through SSE streams, not request/response JSON.

Installation

<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar.js"></script>

Core Syntax

Signals (State)

Prefix with $ in expressions. Define with data-signals:

<div data-signals:count="0">
  <span data-text="$count"></span>
  <button data-on:click="$count++">+1</button>
</div>

Nested: data-signals:user.name="''" or data-signals="{user: {name: '', age: 0}}".

Actions (Backend Calls)

Use @verb(url) syntax:

<button data-on:click="@get('/api/data')">Load</button>
<button data-on:click="@post('/api/save')">Save</button>
<button data-on:click="@delete('/api/item')">Delete</button>

All signals automatically sent. Filter with options: @get('/api', {filterSignals: {include: /^form/}}).

Event Modifiers

Double-underscore syntax with dot parameters:

<input data-on:input__debounce.300ms="@get('/search')">
<button data-on:click__once="@post('/submit')">
<div data-on:click__outside="$open = false">

Common: __debounce.Xms, __throttle.Xms, __once, __prevent, __stop, __window, __outside.

Attributes Quick Reference

Attribute Purpose Example
data-signals Define reactive state data-signals:foo="1"
data-computed Derived state (read-only) data-computed:full="$first+' '+$last"
data-text Bind text content data-text="$foo"
data-bind Two-way form binding data-bind:query
data-show Toggle visibility (CSS) data-show="$loading"
data-class Conditional classes data-class:active="$selected"
data-on Event handlers data-on:click="@post('/x')"
data-indicator Loading state signal data-indicator:fetching
data-attr Dynamic attributes data-attr:disabled="$busy"
data-ref DOM element reference data-ref:myEl then $myEl.focus()

SSE Response Format (Critical)

Datastar expects text/event-stream, not HTML or JSON. Server pushes events:

Patch elements:

event: datastar-patch-elements
data: elements <div id="results">New content</div>

Patch signals:

event: datastar-patch-signals
data: signals {"count": 5}

Remove elements:

event: datastar-patch-elements
data: mode remove
data: selector #old-item

Patch modes: morph (default), inner, outer, prepend, append, before, after, remove.

Note: blank line after each event block.

Go SDK

import "github.com/starfederation/datastar-go/datastar"

func handler(w http.ResponseWriter, r *http.Request) {
    // Read signals from request
    var store MyStore
    if err := datastar.ReadSignals(r, &store); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    // Create SSE writer
    sse := datastar.NewSSE(w, r)

    // Send HTML fragment (patches by ID match)
    sse.PatchElements(`<div id="result">Updated!</div>`)

    // Update signals
    sse.MarshalAndPatchSignals(map[string]any{"count": 42})

    // Execute script
    sse.ExecuteScript(`console.log('done')`)
}

With Templ

func handler(w http.ResponseWriter, r *http.Request) {
    sse := datastar.NewSSE(w, r)
    sse.PatchElementTempl(MyComponent(data))
}

Complete Example: Counter

HTML:

<div data-signals:count="0">
  <p>Count: <span data-text="$count"></span></p>
  <button data-on:click="@post('/increment')">+1</button>
</div>

Go:

type Store struct {
    Count int `json:"count"`
}

func incrementHandler(w http.ResponseWriter, r *http.Request) {
    var store Store
    datastar.ReadSignals(r, &store)
    store.Count++

    sse := datastar.NewSSE(w, r)
    sse.MarshalAndPatchSignals(store)
}

Complete Example: Search with Debounce

HTML:

<div data-signals:query="''">
  <input
    data-bind:query
    data-on:input__debounce.300ms="@get('/search')"
    data-indicator:searching
  >
  <span data-show="$searching">Loading...</span>
  <div id="results"></div>
</div>

Go:

func searchHandler(w http.ResponseWriter, r *http.Request) {
    var store struct {
        Query string `json:"query"`
    }
    datastar.ReadSignals(r, &store)

    results := search(store.Query)

    sse := datastar.NewSSE(w, r)
    sse.PatchElements(renderResults(results))
}

Common Mistakes

Wrong Right Why
data-store data-signals Different framework
data-on-click data-on:click Colon separates event
count in expr $count Signals need $ prefix
$post('/x') @post('/x') Actions use @ prefix
__debounce_300 __debounce.300ms Dot separates param, include unit
Return JSON Return SSE Must use text/event-stream
http.ResponseWriter datastar.NewSSE(w, r) Need SSE helper
MergeFragments PatchElements SDK uses "Patch" not "Merge"
MergeSignals MarshalAndPatchSignals SDK uses "Patch" terminology