# Datastar Docs Read the full-page docs at [data-star.dev/docs](https://data-star.dev/docs) for the best experience. ## Guide ### Getting Started Datastar simplifies frontend development, allowing you to build backend-driven, interactive UIs using a [hypermedia-first](https://hypermedia.systems/hypermedia-a-reintroduction/) approach that extends and enhances HTML. Datastar provides backend reactivity like [htmx](https://htmx.org/) and frontend reactivity like [Alpine.js](https://alpinejs.dev/) in a lightweight frontend framework that doesn’t require any npm packages or other dependencies. It provides two primary functions: . Modify the DOM and state by sending events from your backend. . Build reactivity into your frontend using standard `data-*` HTML attributes. > Other useful resources include an AI-generated [deep wiki](https://deepwiki.com/starfederation/datastar), LLM-ingestible [code samples](https://context7.com/websites/data-star_dev), and [single-page docs](https://data-star.dev/docs). ## Installation The quickest way to use Datastar is to include it using a `script` tag that fetches it from a CDN. ``` ``` If you prefer to host the file yourself, download the [script](https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.7/bundles/datastar.js) or create your own bundle using the [bundler](https://data-star.dev/bundler), then include it from the appropriate path. ``` ``` To import Datastar using a package manager such as npm, Deno, or Bun, you can use an import statement. ``` // @ts-expect-error (only required for TypeScript projects) import 'https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.7/bundles/datastar.js' ``` ## `data-*` At the core of Datastar are `data-*` HTML attributes (hence the name). They allow you to add reactivity to your frontend and interact with your backend in a declarative way. > The Datastar [VSCode extension](https://marketplace.visualstudio.com/items?itemName=starfederation.datastar-vscode) and [IntelliJ plugin](https://plugins.jetbrains.com/plugin/26072-datastar-support) provide autocompletion for all available `data-*` attributes. The [`data-on`](https://data-star.dev/reference/attributes#data-on) attribute can be used to attach an event listener to an element and execute an expression whenever the event is triggered. The value of the attribute is a [Datastar expression](https://data-star.dev/guide/datastar_expressions) in which JavaScript can be used. ``` ``` Demo Open the pod bay doors, HAL. We’ll explore more data attributes in the [next section of the guide](https://data-star.dev/guide/reactive_signals). ## Patching Elements With Datastar, the backend *drives* the frontend by **patching** (adding, updating and removing) HTML elements in the DOM. Datastar receives elements from the backend and manipulates the DOM using a morphing strategy (by default). Morphing ensures that only modified parts of the DOM are updated, and that only data attributes that have changed are [reapplied](https://data-star.dev/reference/attributes#attribute-evaluation-order), preserving state and improving performance. Datastar provides [actions](https://data-star.dev/reference/actions#backend-actions) for sending requests to the backend. The [`@get()`](https://data-star.dev/reference/actions#get) action sends a `GET` request to the provided URL using a [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) request. ```
``` > Actions in Datastar are helper functions that have the syntax `@actionName()`. Read more about actions in the [reference](https://data-star.dev/reference/actions). If the response has a `content-type` of `text/html`, the top-level HTML elements will be morphed into the existing DOM based on the element IDs. ```
I’m sorry, Dave. I’m afraid I can’t do that.
``` We call this a “Patch Elements” event because multiple elements can be patched into the DOM at once. Demo Open the pod bay doors, HAL. `Waiting for an order...` In the example above, the DOM must contain an element with a `hal` ID in order for morphing to work. Other [patching strategies](https://data-star.dev/reference/sse_events#datastar-patch-elements) are available, but morph is the best and simplest choice in most scenarios. If the response has a `content-type` of `text/event-stream`, it can contain zero or more [SSE events](https://data-star.dev/reference/sse_events). The example above can be replicated using a `datastar-patch-elements` SSE event. ``` event: datastar-patch-elements data: elements
data: elements I’m sorry, Dave. I’m afraid I can’t do that. data: elements
``` Because we can send as many events as we want in a stream, and because it can be a long-lived connection, we can extend the example above to first send HAL’s response and then, after a few seconds, reset the text. ``` event: datastar-patch-elements data: elements
data: elements I’m sorry, Dave. I’m afraid I can’t do that. data: elements
event: datastar-patch-elements data: elements
data: elements Waiting for an order... data: elements
``` Demo Open the pod bay doors, HAL. `Waiting for an order...` Here’s the code to generate the SSE events above using the SDKs. ``` ;; Import the SDK's api and your adapter (require '[starfederation.datastar.clojure.api :as d*] '[starfederation.datastar.clojure.adapter.http-kit :refer [->sse-response on-open]]) ;; in a ring handler (defn handler [request] ;; Create an SSE response (->sse-response request {on-open (fn [sse] ;; Patches elements into the DOM (d*/patch-elements! sse "
I’m sorry, Dave. I’m afraid I can’t do that.
") (Thread/sleep 1000) (d*/patch-elements! sse "
Waiting for an order...
"))})) ``` ``` using StarFederation.Datastar.DependencyInjection; // Adds Datastar as a service builder.Services.AddDatastar(); app.MapGet("/", async (IDatastarService datastarService) => { // Patches elements into the DOM. await datastarService.PatchElementsAsync(@"
I’m sorry, Dave. I’m afraid I can’t do that.
"); await Task.Delay(TimeSpan.FromSeconds(1)); await datastarService.PatchElementsAsync(@"
Waiting for an order...
"); }); ``` ``` import ( "github.com/starfederation/datastar-go/datastar" time ) // Creates a new `ServerSentEventGenerator` instance. sse := datastar.NewSSE(w,r) // Patches elements into the DOM. sse.PatchElements( `
I’m sorry, Dave. I’m afraid I can’t do that.
` ) time.Sleep(1 * time.Second) sse.PatchElements( `
Waiting for an order...
` ) ``` ``` import starfederation.datastar.utils.ServerSentEventGenerator; // Creates a new `ServerSentEventGenerator` instance. AbstractResponseAdapter responseAdapter = new HttpServletResponseAdapter(response); ServerSentEventGenerator generator = new ServerSentEventGenerator(responseAdapter); // Patches elements into the DOM. generator.send(PatchElements.builder() .data("
I’m sorry, Dave. I’m afraid I can’t do that.
") .build() ); Thread.sleep(1000); generator.send(PatchElements.builder() .data("
Waiting for an order...
") .build() ); ``` ``` val generator = ServerSentEventGenerator(response) generator.patchElements( elements = """
I’m sorry, Dave. I’m afraid I can’t do that.
""", ) Thread.sleep(ONE_SECOND) generator.patchElements( elements = """
Waiting for an order...
""", ) ``` ``` use starfederation\datastar\ServerSentEventGenerator; // Creates a new `ServerSentEventGenerator` instance. $sse = new ServerSentEventGenerator(); // Patches elements into the DOM. $sse->patchElements( '
I’m sorry, Dave. I’m afraid I can’t do that.
' ); sleep(1); $sse->patchElements( '
Waiting for an order...
' ); ``` ``` from datastar_py import ServerSentEventGenerator as SSE from datastar_py.sanic import datastar_response @app.get('/open-the-bay-doors') @datastar_response async def open_doors(request): yield SSE.patch_elements('
I’m sorry, Dave. I’m afraid I can’t do that.
') await asyncio.sleep(1) yield SSE.patch_elements('
Waiting for an order...
') ``` ``` require 'datastar' # Create a Datastar::Dispatcher instance datastar = Datastar.new(request:, response:) # In a Rack handler, you can instantiate from the Rack env # datastar = Datastar.from_rack_env(env) # Start a streaming response datastar.stream do |sse| # Patches elements into the DOM. sse.patch_elements %(
I’m sorry, Dave. I’m afraid I can’t do that.
) sleep 1 sse.patch_elements %(
Waiting for an order...
) end ``` ``` use async_stream::stream; use datastar::prelude::*; use std::thread; use std::time::Duration; Sse(stream! { // Patches elements into the DOM. yield PatchElements::new("
I’m sorry, Dave. I’m afraid I can’t do that.
").into(); thread::sleep(Duration::from_secs(1)); yield PatchElements::new("
Waiting for an order...
").into(); }) ``` ``` // Creates a new `ServerSentEventGenerator` instance (this also sends required headers) ServerSentEventGenerator.stream(req, res, (stream) => { // Patches elements into the DOM. stream.patchElements(`
I’m sorry, Dave. I’m afraid I can’t do that.
`); setTimeout(() => { stream.patchElements(`
Waiting for an order...
`); }, 1000); }); ``` > In addition to your browser’s dev tools, the [Datastar Inspector](https://data-star.dev/datastar_pro#datastar-inspector) can be used to monitor and inspect SSE events received by Datastar. We’ll cover event streams and [SSE events](https://data-star.dev/reference/sse_events) in more detail [later in the guide](https://data-star.dev/guide/backend_requests), but as you can see, they are just plain text events with a special syntax, made simpler by the [SDKs](https://data-star.dev/reference/sdks). ### Reactive Signals In a hypermedia approach, the backend drives state to the frontend and acts as the primary source of truth. It’s up to the backend to determine what actions the user can take next by patching appropriate elements in the DOM. Sometimes, however, you may need access to frontend state that’s driven by user interactions. Click, input and keydown events are some of the more common user events that you’ll want your frontend to be able to react to. Datastar uses *signals* to manage frontend state. You can think of signals as reactive variables that automatically track and propagate changes in and to [Datastar expressions](https://data-star.dev/guide/datastar_expressions). Signals are denoted using the `$` prefix. ## Data Attributes Datastar allows you to add reactivity to your frontend and interact with your backend in a declarative way using [custom `data-*` attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/data-*). > The Datastar [VSCode extension](https://marketplace.visualstudio.com/items?itemName=starfederation.datastar-vscode) and [IntelliJ plugin](https://plugins.jetbrains.com/plugin/26072-datastar-support) provide autocompletion for all available `data-*` attributes. ### `data-bind` The [`data-bind`](https://data-star.dev/reference/attributes#data-bind) attribute sets up two-way data binding on any HTML element that receives user input or selections. These include `input`, `textarea`, `select`, `checkbox` and `radio` elements, as well as web components whose value can be made reactive. ``` ``` This creates a new signal that can be called using `$foo`, and binds it to the element’s value. If either is changed, the other automatically updates. You can accomplish the same thing passing the signal name as a *value*. This syntax can be more convenient to use with some templating languages. ``` ``` According to the [HTML spec](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*), all [`data-*`](https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/Use_data_attributes) attributes are case-insensitive. When Datastar processes these attributes, hyphenated names are automatically converted to camel case by removing hyphens and uppercasing the letter following each hyphen. For example, `data-bind:foo-bar` creates a signal named `$fooBar`. ``` ``` Read more about [attribute casing](https://data-star.dev/reference/attributes#attribute-casing) in the reference. ### `data-text` The [`data-text`](https://data-star.dev/reference/attributes#data-text) attribute sets the text content of an element to the value of a signal. The `$` prefix is required to denote a signal. ```
``` Demo ``` ``` The value of the `data-text` attribute is a [Datastar expression](https://data-star.dev/guide/datastar_expressions) that is evaluated, meaning that we can use JavaScript in it. ```
``` Demo ``` ``` ### `data-computed` The [`data-computed`](https://data-star.dev/reference/attributes#data-computed) attribute creates a new signal that is derived from a reactive expression. The computed signal is read-only, and its value is automatically updated when any signals in the expression are updated. ```
``` This results in the `$repeated` signal’s value always being equal to the value of the `$fooBar` signal repeated twice. Computed signals are useful for memoizing expressions containing other signals. Demo ``` ``` ### `data-show` The [`data-show`](https://data-star.dev/reference/attributes#data-show) attribute can be used to show or hide an element based on whether an expression evaluates to `true` or `false`. ``` ``` This results in the button being visible only when the input value is *not* an empty string. This could also be shortened to `data-show="$fooBar"`. Demo Save Since the button is visible until Datastar processes the `data-show` attribute, it’s a good idea to set its initial style to `display: none` to prevent a flash of unwanted content. ``` ``` ### `data-class` The [`data-class`](https://data-star.dev/reference/attributes#data-class) attribute allows us to add or remove an element’s class based on an expression. ``` ``` If the expression evaluates to `true`, the `success` class is added to the element, otherwise it is removed. Demo Save Unlike the `data-bind` attribute, in which hyphenated names are converted to camel case, the `data-class` attribute converts the class name to kebab case. For example, `data-class:font-bold` adds or removes the `font-bold` class. ``` ``` The `data-class` attribute can also be used to add or remove multiple classes from an element using a set of key-value pairs, where the keys represent class names and the values represent expressions. ``` ``` Note how the `font-bold` key must be wrapped in quotes because it contains a hyphen. ### `data-attr` The [`data-attr`](https://data-star.dev/reference/attributes#data-attr) attribute can be used to bind the value of any HTML attribute to an expression. ``` ``` This results in a `disabled` attribute being given the value `true` whenever the input is an empty string. Demo Save The `data-attr` attribute also converts the attribute name to kebab case, since HTML attributes are typically written in kebab case. For example, `data-attr:aria-hidden` sets the value of the `aria-hidden` attribute. ``` ``` The `data-attr` attribute can also be used to set the values of multiple attributes on an element using a set of key-value pairs, where the keys represent attribute names and the values represent expressions. ``` ``` Note how the `aria-hidden` key must be wrapped in quotes because it contains a hyphen. ### `data-signals` Signals are globally accessible from anywhere in the DOM. So far, we’ve created signals on the fly using `data-bind` and `data-computed`. If a signal is used without having been created, it will be created automatically and its value set to an empty string. Another way to create signals is using the [`data-signals`](https://data-star.dev/reference/attributes#data-signals) attribute, which patches (adds, updates or removes) one or more signals into the existing signals. ```
``` Signals can be nested using dot-notation. ```
``` Like the `data-bind` attribute, hyphenated names used with `data-signals` are automatically converted to camel case by removing hyphens and uppercasing the letter following each hyphen. ```
``` The `data-signals` attribute can also be used to patch multiple signals using a set of key-value pairs, where the keys represent signal names and the values represent expressions. Nested signals can be created using nested objects. ```
``` ### `data-on` The [`data-on`](https://data-star.dev/reference/attributes#data-on) attribute can be used to attach an event listener to an element and run an expression whenever the event is triggered. ``` ``` This results in the `$foo` signal’s value being set to an empty string whenever the button element is clicked. This can be used with any valid event name such as `data-on:keydown`, `data-on:mouseover`, etc. Demo Reset Custom events can also be used. Like the `data-class` attribute, the `data-on` attribute converts the event name to kebab case. For example, `data-on:custom-event` listens for the `custom-event` event. ```
``` These are just *some* of the attributes available in Datastar. For a complete list, see the [attribute reference](https://data-star.dev/reference/attributes). ## Frontend Reactivity Datastar’s data attributes enable declarative signals and expressions, providing a simple yet powerful way to add reactivity to the frontend. Datastar expressions are strings that are evaluated by Datastar [attributes](https://data-star.dev/reference/attributes) and [actions](https://data-star.dev/reference/actions). While they are similar to JavaScript, there are some important differences that are explained in the [next section of the guide](https://data-star.dev/guide/datastar_expressions). ```
``` Demo HAL, do you read me? ``` ``` See if you can figure out what the code below does based on what you’ve learned so far, *before* trying the demo below it. ```
What do you put in a toaster?
You answered “”. That is correct ✅ The correct answer is “ ” 🤷
``` Demo What do you put in a toaster? BUZZ You answered “”. That is correct ✅ The correct answer is “bread” 🤷 > The [Datastar Inspector](https://data-star.dev/datastar_pro#datastar-inspector) can be used to inspect and filter current signals and view signal patch events in real-time. ## Patching Signals Remember that in a hypermedia approach, the backend drives state to the frontend. Just like with elements, frontend signals can be **patched** (added, updated and removed) from the backend using [backend actions](https://data-star.dev/reference/actions#backend-actions). ```
``` If a response has a `content-type` of `application/json`, the signal values are patched into the frontend signals. We call this a “Patch Signals” event because multiple signals can be patched (using [JSON Merge Patch RFC 7396](https://datatracker.ietf.org/doc/rfc7396/)) into the existing signals. ``` {"hal": "Affirmative, Dave. I read you."} ``` Demo HAL, do you read me? Reset If the response has a `content-type` of `text/event-stream`, it can contain zero or more [SSE events](https://data-star.dev/reference/sse_events). The example above can be replicated using a `datastar-patch-signals` SSE event. ``` event: datastar-patch-signals data: signals {hal: 'Affirmative, Dave. I read you.'} ``` Because we can send as many events as we want in a stream, and because it can be a long-lived connection, we can extend the example above to first set the `hal` signal to an “affirmative” response and then, after a second, reset the signal. ``` event: datastar-patch-signals data: signals {hal: 'Affirmative, Dave. I read you.'} // Wait 1 second event: datastar-patch-signals data: signals {hal: '...'} ``` Demo HAL, do you read me? Here’s the code to generate the SSE events above using the SDKs. ``` ;; Import the SDK's api and your adapter (require '[starfederation.datastar.clojure.api :as d*] '[starfederation.datastar.clojure.adapter.http-kit :refer [->sse-response on-open]]) ;; in a ring handler (defn handler [request] ;; Create an SSE response (->sse-response request {on-open (fn [sse] ;; Patches signal. (d*/patch-signals! sse "{hal: 'Affirmative, Dave. I read you.'}") (Thread/sleep 1000) (d*/patch-signals! sse "{hal: '...'}"))})) ``` ``` using StarFederation.Datastar.DependencyInjection; // Adds Datastar as a service builder.Services.AddDatastar(); app.MapGet("/hal", async (IDatastarService datastarService) => { // Patches signals. await datastarService.PatchSignalsAsync(new { hal = "Affirmative, Dave. I read you" }); await Task.Delay(TimeSpan.FromSeconds(3)); await datastarService.PatchSignalsAsync(new { hal = "..." }); }); ``` ``` import ( "github.com/starfederation/datastar-go/datastar" ) // Creates a new `ServerSentEventGenerator` instance. sse := datastar.NewSSE(w, r) // Patches signals sse.PatchSignals([]byte(`{hal: 'Affirmative, Dave. I read you.'}`)) time.Sleep(1 * time.Second) sse.PatchSignals([]byte(`{hal: '...'}`)) ``` ``` import starfederation.datastar.utils.ServerSentEventGenerator; // Creates a new `ServerSentEventGenerator` instance. AbstractResponseAdapter responseAdapter = new HttpServletResponseAdapter(response); ServerSentEventGenerator generator = new ServerSentEventGenerator(responseAdapter); // Patches signals. generator.send(PatchSignals.builder() .data("{\"hal\": \"Affirmative, Dave. I read you.\"}") .build() ); Thread.sleep(1000); generator.send(PatchSignals.builder() .data("{\"hal\": \"...\"}") .build() ); ``` ``` val generator = ServerSentEventGenerator(response) generator.patchSignals( signals = """{"hal": "Affirmative, Dave. I read you."}""", ) Thread.sleep(ONE_SECOND) generator.patchSignals( signals = """{"hal": "..."}""", ) ``` ``` use starfederation\datastar\ServerSentEventGenerator; // Creates a new `ServerSentEventGenerator` instance. $sse = new ServerSentEventGenerator(); // Patches signals. $sse->patchSignals(['hal' => 'Affirmative, Dave. I read you.']); sleep(1); $sse->patchSignals(['hal' => '...']); ``` ``` from datastar_py import ServerSentEventGenerator as SSE from datastar_py.sanic import datastar_response @app.get('/do-you-read-me') @datastar_response async def open_doors(request): yield SSE.patch_signals({"hal": "Affirmative, Dave. I read you."}) await asyncio.sleep(1) yield SSE.patch_signals({"hal": "..."}) ``` ``` require 'datastar' # Create a Datastar::Dispatcher instance datastar = Datastar.new(request:, response:) # In a Rack handler, you can instantiate from the Rack env # datastar = Datastar.from_rack_env(env) # Start a streaming response datastar.stream do |sse| # Patches signals sse.patch_signals(hal: 'Affirmative, Dave. I read you.') sleep 1 sse.patch_signals(hal: '...') end ``` ``` use async_stream::stream; use datastar::prelude::*; use std::thread; use std::time::Duration; Sse(stream! { // Patches signals. yield PatchSignals::new("{hal: 'Affirmative, Dave. I read you.'}").into(); thread::sleep(Duration::from_secs(1)); yield PatchSignals::new("{hal: '...'}").into(); }) ``` ``` // Creates a new `ServerSentEventGenerator` instance (this also sends required headers) ServerSentEventGenerator.stream(req, res, (stream) => { // Patches signals. stream.patchSignals({'hal': 'Affirmative, Dave. I read you.'}); setTimeout(() => { stream.patchSignals({'hal': '...'}); }, 1000); }); ``` > In addition to your browser’s dev tools, the [Datastar Inspector](https://data-star.dev/datastar_pro#datastar-inspector) can be used to monitor and inspect SSE events received by Datastar. We’ll cover event streams and [SSE events](https://data-star.dev/reference/sse_events) in more detail [later in the guide](https://data-star.dev/guide/backend_requests), but as you can see, they are just plain text events with a special syntax, made simpler by the [SDKs](https://data-star.dev/reference/sdks). ### Datastar Expressions Datastar expressions are strings that are evaluated by `data-*` attributes. While they are similar to JavaScript, there are some important differences that make them more powerful for declarative hypermedia applications. ## Datastar Expressions The following example outputs `1` because we’ve defined `foo` as a signal with the initial value `1`, and are using `$foo` in a `data-*` attribute. ```
``` A variable `el` is available in every Datastar expression, representing the element that the attribute is attached to. ```
``` When Datastar evaluates the expression `$foo`, it first converts it to the signal value, and then evaluates that expression in a sandboxed context. This means that JavaScript can be used in Datastar expressions. ```
``` JavaScript operators are also available in Datastar expressions. This includes (but is not limited to) the ternary operator `?:`, the logical OR operator `||`, and the logical AND operator `&&`. These operators are helpful in keeping Datastar expressions terse. ``` // Output one of two values, depending on the truthiness of a signal
// Show a countdown if the signal is truthy or the time remaining is less than 10 seconds
Countdown
// Only send a request if the signal is truthy ``` Multiple statements can be used in a single expression by separating them with a semicolon. ```
``` Expressions may span multiple lines, but a semicolon must be used to separate statements. Unlike JavaScript, line breaks alone are not sufficient to separate statements. ```
``` ## Using JavaScript Most of your JavaScript logic should go in `data-*` attributes, since reactive signals and actions only work in [Datastar expressions](https://data-star.dev/guide/datastar_expressions). > Caution: if you find yourself trying to do too much in Datastar expressions, **you are probably overcomplicating it™**. Any JavaScript functionality you require that cannot belong in `data-*` attributes should be extracted out into [external scripts](#external-scripts) or, better yet, [web components](#web-components). > Always encapsulate state and send **props down, events up**. ### External Scripts When using external scripts, you should pass data into functions via arguments and return a result. Alternatively, listen for custom events dispatched from them (props down, events up). In this way, the function is encapsulated – all it knows is that it receives input via an argument, acts on it, and optionally returns a result or dispatches a custom event – and `data-*` attributes can be used to drive reactivity. ```
``` ``` function myfunction(data) { return `You entered: ${data}`; } ``` If your function call is asynchronous then it will need to dispatch a custom event containing the result. While asynchronous code *can* be placed within Datastar expressions, Datastar will *not* await it. ```
``` ``` async function myfunction(element, data) { const value = await new Promise((resolve) => { setTimeout(() => resolve(`You entered: ${data}`), 1000); }); element.dispatchEvent( new CustomEvent('mycustomevent', {detail: {value}}) ); } ``` See the [sortable example](https://data-star.dev/examples/sortable). ### Web Components [Web components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) allow you create reusable, encapsulated, custom elements. They are native to the web and require no external libraries or frameworks. Web components unlock [custom elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) – HTML tags with custom behavior and styling. When using web components, pass data into them via attributes and listen for custom events dispatched from them (*props down, events up*). In this way, the web component is encapsulated – all it knows is that it receives input via an attribute, acts on it, and optionally dispatches a custom event containing the result – and `data-*` attributes can be used to drive reactivity. ```
``` ``` class MyComponent extends HTMLElement { static get observedAttributes() { return ['src']; } attributeChangedCallback(name, oldValue, newValue) { const value = `You entered: ${newValue}`; this.dispatchEvent( new CustomEvent('mycustomevent', {detail: {value}}) ); } } customElements.define('my-component', MyComponent); ``` Since the `value` attribute is allowed on web components, it is also possible to use `data-bind` to bind a signal to the web component’s value. Note that a `change` event must be dispatched so that the event listener used by `data-bind` is triggered by the value change. See the [web component example](https://data-star.dev/examples/web_component). ## Executing Scripts Just like elements and signals, the backend can also send JavaScript to be executed on the frontend using [backend actions](https://data-star.dev/reference/actions#backend-actions). ``` ``` If a response has a `content-type` of `text/javascript`, the value will be executed as JavaScript in the browser. ``` alert('This mission is too important for me to allow you to jeopardize it.') ``` Demo What are you talking about, HAL? If the response has a `content-type` of `text/event-stream`, it can contain zero or more [SSE events](https://data-star.dev/reference/sse_events). The example above can be replicated by including a `script` tag inside of a `datastar-patch-elements` SSE event. ``` event: datastar-patch-elements data: elements
data: elements data: elements
``` If you *only* want to execute a script, you can `append` the script tag to the `body`. ``` event: datastar-patch-elements data: mode append data: selector body data: elements ``` Most SDKs have an `ExecuteScript` helper function for executing a script. Here’s the code to generate the SSE event above using the Go SDK. ``` sse := datastar.NewSSE(writer, request) sse.ExecuteScript(`alert('This mission is too important for me to allow you to jeopardize it.')`) ``` Demo What are you talking about, HAL? We’ll cover event streams and [SSE events](https://data-star.dev/reference/sse_events) in more detail [later in the guide](https://data-star.dev/guide/backend_requests), but as you can see, they are just plain text events with a special syntax, made simpler by the [SDKs](https://data-star.dev/reference/sdks). ### Backend Requests Between [attributes](https://data-star.dev/reference/attributes) and [actions](https://data-star.dev/reference/actions), Datastar provides you with everything you need to build hypermedia-driven applications. Using this approach, the backend drives state to the frontend and acts as the single source of truth, determining what actions the user can take next. ## Sending Signals By default, all signals (except for local signals whose keys begin with an underscore) are sent in an object with every backend request. When using a `GET` request, the signals are sent as a `datastar` query parameter, otherwise they are sent as a JSON body. By sending **all** signals in every request, the backend has full access to the frontend state. This is by design. It is **not** recommended to send partial signals, but if you must, you can use the [`filterSignals`](https://data-star.dev/reference/actions#filterSignals) option to filter the signals sent to the backend. ### Nesting Signals Signals can be nested, making it easier to target signals in a more granular way on the backend. Using dot-notation: ```
``` Using object syntax: ```
``` Using two-way binding: ``` ``` A practical use-case of nested signals is when you have repetition of state on a page. The following example tracks the open/closed state of a menu on both desktop and mobile devices, and the [toggleAll()](https://data-star.dev/reference/actions#toggleAll) action to toggle the state of all menus at once. ```
``` ## Reading Signals To read signals from the backend, JSON decode the `datastar` query param for `GET` requests, and the request body for all other methods. All [SDKs](https://data-star.dev/reference/sdks) provide a helper function to read signals. Here’s how you would read the nested signal `foo.bar` from an incoming request. ``` using StarFederation.Datastar.DependencyInjection; // Adds Datastar as a service builder.Services.AddDatastar(); public record Signals { [JsonPropertyName("foo")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public FooSignals? Foo { get; set; } = null; public record FooSignals { [JsonPropertyName("bar")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Bar { get; set; } } } app.MapGet("/read-signals", async (IDatastarService datastarService) => { Signals? mySignals = await datastarService.ReadSignalsAsync(); var bar = mySignals?.Foo?.Bar; }); ``` ``` import ("github.com/starfederation/datastar-go/datastar") type Signals struct { Foo struct { Bar string `json:"bar"` } `json:"foo"` } signals := &Signals{} if err := datastar.ReadSignals(request, signals); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } ``` ``` @Serializable data class Signals( val foo: String, ) val jsonUnmarshaller: JsonUnmarshaller = { json -> Json.decodeFromString(json) } val request: Request = postRequest( body = """ { "foo": "bar" } """.trimIndent(), ) val signals = readSignals(request, jsonUnmarshaller) ``` ``` use starfederation\datastar\ServerSentEventGenerator; // Reads all signals from the request. $signals = ServerSentEventGenerator::readSignals(); ``` ``` from datastar_py.fastapi import datastar_response, read_signals @app.get("/updates") @datastar_response async def updates(request: Request): # Retrieve a dictionary with the current state of the signals from the frontend signals = await read_signals(request) ``` ``` # Setup with request datastar = Datastar.new(request:, response:) # Read signals some_signal = datastar.signals[:some_signal] ``` ## SSE Events Datastar can stream zero or more [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) (SSE) from the web server to the browser. There’s no special backend plumbing required to use SSE, just some special syntax. Fortunately, SSE is straightforward and [provides us with some advantages](https://data-star.dev/essays/event_streams_all_the_way_down), in addition to allowing us to send multiple events in a single response (in contrast to sending `text/html` or `application/json` responses). First, set up your backend in the language of your choice. Familiarize yourself with [sending SSE events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#sending_events_from_the_server), or use one of the backend [SDKs](https://data-star.dev/reference/sdks) to get up and running even faster. We’re going to use the SDKs in the examples below, which set the appropriate headers and format the events for us. The following code would exist in a controller action endpoint in your backend. ``` ;; Import the SDK's api and your adapter (require '[starfederation.datastar.clojure.api :as d*] '[starfederation.datastar.clojure.adapter.http-kit :refer [->sse-response on-open]]) ;; in a ring handler (defn handler [request] ;; Create an SSE response (->sse-response request {on-open (fn [sse] ;; Patches elements into the DOM (d*/patch-elements! sse "
What do you put in a toaster?
") ;; Patches signals (d*/patch-signals! sse "{response: '', answer: 'bread'}"))})) ``` ``` using StarFederation.Datastar.DependencyInjection; // Adds Datastar as a service builder.Services.AddDatastar(); app.MapGet("/", async (IDatastarService datastarService) => { // Patches elements into the DOM. await datastarService.PatchElementsAsync(@"
What do you put in a toaster?
"); // Patches signals. await datastarService.PatchSignalsAsync(new { response = "", answer = "bread" }); }); ``` ``` import ("github.com/starfederation/datastar-go/datastar") // Creates a new `ServerSentEventGenerator` instance. sse := datastar.NewSSE(w,r) // Patches elements into the DOM. sse.PatchElements( `
What do you put in a toaster?
` ) // Patches signals. sse.PatchSignals([]byte(`{response: '', answer: 'bread'}`)) ``` ``` import starfederation.datastar.utils.ServerSentEventGenerator; // Creates a new `ServerSentEventGenerator` instance. AbstractResponseAdapter responseAdapter = new HttpServletResponseAdapter(response); ServerSentEventGenerator generator = new ServerSentEventGenerator(responseAdapter); // Patches elements into the DOM. generator.send(PatchElements.builder() .data("
What do you put in a toaster?
") .build() ); // Patches signals. generator.send(PatchSignals.builder() .data("{\"response\": \"\", \"answer\": \"\"}") .build() ); ``` ``` val generator = ServerSentEventGenerator(response) generator.patchElements( elements = """
What do you put in a toaster?
""", ) generator.patchSignals( signals = """{"response": "", "answer": "bread"}""", ) ``` ``` use starfederation\datastar\ServerSentEventGenerator; // Creates a new `ServerSentEventGenerator` instance. $sse = new ServerSentEventGenerator(); // Patches elements into the DOM. $sse->patchElements( '
What do you put in a toaster?
' ); // Patches signals. $sse->patchSignals(['response' => '', 'answer' => 'bread']); ``` ``` from datastar_py import ServerSentEventGenerator as SSE from datastar_py.litestar import DatastarResponse async def endpoint(): return DatastarResponse([ SSE.patch_elements('
What do you put in a toaster?
'), SSE.patch_signals({"response": "", "answer": "bread"}) ]) ``` ``` require 'datastar' # Create a Datastar::Dispatcher instance datastar = Datastar.new(request:, response:) # In a Rack handler, you can instantiate from the Rack env # datastar = Datastar.from_rack_env(env) # Start a streaming response datastar.stream do |sse| # Patches elements into the DOM sse.patch_elements %(
What do you put in a toaster?
) # Patches signals sse.patch_signals(response: '', answer: 'bread') end ``` ``` use datastar::prelude::*; use async_stream::stream; Sse(stream! { // Patches elements into the DOM. yield PatchElements::new("
What do you put in a toaster?
").into(); // Patches signals. yield PatchSignals::new("{response: '', answer: 'bread'}").into(); }) ``` ``` // Creates a new `ServerSentEventGenerator` instance (this also sends required headers) ServerSentEventGenerator.stream(req, res, (stream) => { // Patches elements into the DOM. stream.patchElements(`
What do you put in a toaster?
`); // Patches signals. stream.patchSignals({'response': '', 'answer': 'bread'}); }); ``` The `PatchElements()` function updates the provided HTML element into the DOM, replacing the element with `id="question"`. An element with the ID `question` must *already* exist in the DOM. The `PatchSignals()` function updates the `response` and `answer` signals into the frontend signals. With our backend in place, we can now use the `data-on:click` attribute to trigger the [`@get()`](https://data-star.dev/reference/actions#get) action, which sends a `GET` request to the `/actions/quiz` endpoint on the server when a button is clicked. ```
You answered “”. That is correct ✅ The correct answer is “” 🤷
``` Now when the `Fetch a question` button is clicked, the server will respond with an event to modify the `question` element in the DOM and an event to modify the `response` and `answer` signals. We’re driving state from the backend! Demo ... Fetch a question BUZZ You answered “”. That is correct ✅ The correct answer is “” 🤷 ### `data-indicator` The [`data-indicator`](https://data-star.dev/reference/attributes#data-indicator) attribute sets the value of a signal to `true` while the request is in flight, otherwise `false`. We can use this signal to show a loading indicator, which may be desirable for slower responses. ```
``` Demo ... Fetch a question ![Indicator](https://data-star.dev/cdn-cgi/image/format=auto,width=32/static/images/rocket-animated-1d781383a0d7cbb1eb575806abeec107c8a915806fb55ee19e4e33e8632c75e5.gif) ## Backend Actions We’re not limited to sending just `GET` requests. Datastar provides [backend actions](https://data-star.dev/reference/actions#backend-actions) for each of the methods available: `@get()`, `@post()`, `@put()`, `@patch()` and `@delete()`. Here’s how we can send an answer to the server for processing, using a `POST` request. ``` ``` One of the benefits of using SSE is that we can send multiple events (patch elements and patch signals) in a single response. ``` (d*/patch-elements! sse "
...
") (d*/patch-elements! sse "
...
") (d*/patch-signals! sse "{answer: '...', prize: '...'}") ``` ``` datastarService.PatchElementsAsync(@"
...
"); datastarService.PatchElementsAsync(@"
...
"); datastarService.PatchSignalsAsync(new { answer = "...", prize = "..." } ); ``` ``` sse.PatchElements(`
...
`) sse.PatchElements(`
...
`) sse.PatchSignals([]byte(`{answer: '...', prize: '...'}`)) ``` ``` generator.send(PatchElements.builder() .data("
...
") .build() ); generator.send(PatchElements.builder() .data("
...
") .build() ); generator.send(PatchSignals.builder() .data("{\"answer\": \"...\", \"prize\": \"...\"}") .build() ); ``` ``` generator.patchElements( elements = """
...
""", ) generator.patchElements( elements = """
...
""", ) generator.patchSignals( signals = """{"answer": "...", "prize": "..."}""", ) ``` ``` $sse->patchElements('
...
'); $sse->patchElements('
...
'); $sse->patchSignals(['answer' => '...', 'prize' => '...']); ``` ``` return DatastarResponse([ SSE.patch_elements('
...
'), SSE.patch_elements('
...
'), SSE.patch_signals({"answer": "...", "prize": "..."}) ]) ``` ``` datastar.stream do |sse| sse.patch_elements('
...
') sse.patch_elements('
...
') sse.patch_signals(answer: '...', prize: '...') end ``` ``` yield PatchElements::new("
...
").into() yield PatchElements::new("
...
").into() yield PatchSignals::new("{answer: '...', prize: '...'}").into() ``` ``` stream.patchElements('
...
'); stream.patchElements('
...
'); stream.patchSignals({'answer': '...', 'prize': '...'}); ``` > In addition to your browser’s dev tools, the [Datastar Inspector](https://data-star.dev/datastar_pro#datastar-inspector) can be used to monitor and inspect SSE events received by Datastar. Read more about SSE events in the [reference](https://data-star.dev/reference/sse_events). ## Congratulations You’ve actually read the entire guide! You should now know how to use Datastar to build reactive applications that communicate with the backend using backend requests and SSE events. Feel free to dive into the [reference](https://data-star.dev/reference) and explore the [examples](https://data-star.dev/examples) next, to learn more about what you can do with Datastar. ### The Tao of Datastar Datastar is just a tool. The Tao of Datastar, or “the Datastar way” as it is often referred to, is a set of opinions from the core team on how to best use Datastar to build maintainable, scalable, high-performance web apps. Ignore them at your own peril! ![The Tao of Datastar](https://data-star.dev/cdn-cgi/image/format=auto,width=640/static/images/tao-of-datastar-454a92131f2d9d30fb17c6e1c86b56833717cc5e25c318738cfa225b0b3c69f0.png) ## State in the Right Place Most state should live in the backend. Since the frontend is exposed to the user, the backend should be the source of truth for your application state. ## Start with the Defaults The default configuration options are the recommended settings for the majority of applications. Start with the defaults, and before you ever get tempted to change them, stop and ask yourself, [well... how did I get here?](https://youtu.be/5IsSpAOD6K8) ## Patch Elements & Signals Since the backend is the source of truth, it should *drive* the frontend by **patching** (adding, updating and removing) HTML elements and signals. ## Use Signals Sparingly Overusing signals typically indicates trying to manage state on the frontend. Favor fetching current state from the backend rather than pre-loading and assuming frontend state is current. A good rule of thumb is to *only* use signals for user interactions (e.g. toggling element visibility) and for sending new state to the backend (e.g. by binding signals to form input elements). ## In Morph We Trust Morphing ensures that only modified parts of the DOM are updated, preserving state and improving performance. This allows you to send down large chunks of the DOM tree (all the way up to the `html` tag), sometimes known as “fat morph”, rather than trying to manage fine-grained updates yourself. If you want to explicitly ignore morphing an element, place the [`data-ignore-morph`](https://data-star.dev/reference/attributes#data-ignore-morph) attribute on it. ## SSE Responses [SSE](https://html.spec.whatwg.org/multipage/server-sent-events.html) responses allow you to send `0` to `n` events, in which you can [patch elements](https://data-star.dev/guide/getting_started/#patching-elements), [patch signals](https://data-star.dev/guide/reactive_signals#patching-signals), and [execute scripts](https://data-star.dev/guide/datastar_expressions#executing-scripts). Since event streams are just HTTP responses with some special formatting that [SDKs](https://data-star.dev/reference/sdks) can handle for you, there’s no real benefit to using a content type other than [`text/event-stream`](https://data-star.dev/reference/actions#response-handling). ## Compression Since SSE responses stream events from the backend and morphing allows sending large chunks of DOM, compressing the response is a natural choice. Compression ratios of 200:1 are not uncommon when compressing streams using Brotli. Read more about compressing streams in [this article](https://andersmurphy.com/2025/04/15/why-you-should-use-brotli-sse.html). ## Backend Templating Since your backend generates your HTML, you can and should use your templating language to [keep things DRY](https://data-star.dev/how_tos/keep_datastar_code_dry) (Don’t Repeat Yourself). ## Page Navigation Page navigation hasn't changed in 30 years. Use the [anchor element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a) (``) to navigate to a new page, or a [redirect](https://data-star.dev/how_tos/redirect_the_page_from_the_backend) if redirecting from the backend. For smooth page transitions, use the [View Transition API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API). ## Browser History Browsers automatically keep a history of pages visited. As soon as you start trying to manage browser history yourself, you are adding complexity. Each page is a resource. Use anchor tags and let the browser do what it is good at. ## CQRS [CQRS](https://martinfowler.com/bliki/CQRS.html), in which commands (writes) and requests (reads) are segregated, makes it possible to have a single long-lived request to receive updates from the backend (reads), while making multiple short-lived requests to the backend (writes). It is a powerful pattern that makes real-time collaboration simple using Datastar. Here’s a basic example. ```
``` ## Loading Indicators Loading indicators inform the user that an action is in progress. Use the [`data-indicator`](https://data-star.dev/reference/attributes#data-indicator) attribute to show loading indicators on elements that trigger backend requests. Here’s an example of a button that shows a loading element while waiting for a response from the backend. ```
``` When using [CQRS](#cqrs), it is generally better to manually show a loading indicator when backend requests are made, and allow it to be hidden when the DOM is updated from the backend. Here’s an example. ```
``` ## Optimistic Updates Optimistic updates (also known as optimistic UI) are when the UI updates immediately as if an operation succeeded, before the backend actually confirms it. It is a strategy used to makes web apps feel snappier, when it in fact deceives the user. Imagine seeing a confirmation message that an action succeeded, only to be shown a second later that it actually failed. Rather than deceive the user, use [loading indicators](#loading-indicators) to show the user that the action is in progress, and only confirm success from the backend (see [this example](https://data-star.dev/examples/rocket_flow)). ## Accessibility The web should be accessible to everyone. Datastar stays out of your way and leaves [accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility) to you. Use semantic HTML, apply ARIA where it makes sense, and ensure your app works well with keyboards and screen readers. Here’s an example of using a[`data-attr`](https://data-star.dev/reference/attributes#data-attr) to apply ARIA attributes to a button than toggles the visibility of a menu. ```
``` ## Reference ### Attributes Data attributes are [evaluated in the order](#attribute-evaluation-order) they appear in the DOM, have special [casing](#attribute-casing) rules, can be [aliased](#aliasing-attributes) to avoid conflicts with other libraries, can contain [Datastar expressions](#datastar-expressions), and have [runtime error handling](#error-handling). > The Datastar [VSCode extension](https://marketplace.visualstudio.com/items?itemName=starfederation.datastar-vscode) and [IntelliJ plugin](https://plugins.jetbrains.com/plugin/26072-datastar-support) provide autocompletion for all available `data-*` attributes. ### `data-attr` Sets the value of any HTML attribute to an expression, and keeps it in sync. ```
``` The `data-attr` attribute can also be used to set the values of multiple attributes on an element using a set of key-value pairs, where the keys represent attribute names and the values represent expressions. ```
``` ### `data-bind` Creates a signal (if one doesn’t already exist) and sets up two-way data binding between it and an element’s value. This means that the value of the element is updated when the signal changes, and the signal value is updated when the value of the element changes. The `data-bind` attribute can be placed on any HTML element on which data can be input or choices selected (`input`, `select`, `textarea` elements, and web components). Event listeners are added for `change` and `input` events. ``` ``` The signal name can be specified in the key (as above), or in the value (as below). This can be useful depending on the templating language you are using. ``` ``` [Attribute casing](#attribute-casing) rules apply to the signal name. ``` ``` The initial value of the signal is set to the value of the element, unless a signal has already been defined. So in the example below, `$fooBar` is set to `baz`. ``` ``` Whereas in the example below, `$fooBar` inherits the value `fizz` of the predefined signal. ```
``` #### Predefined Signal Types When you predefine a signal, its **type** is preserved during binding. Whenever the element’s value changes, the signal value is automatically converted to match the original type. For example, in the code below, `$fooBar` is set to the **number** `10` (not the string `"10"`) when the option is selected. ```
``` In the same way, you can assign multiple input values to a single signal by predefining it as an **array**. In the example below, `$fooBar` becomes `["fizz", "baz"]` when both checkboxes are checked, and `["", ""]` when neither is checked. ```
``` #### File Uploads Input fields of type `file` will automatically encode file contents in base64. This means that a form is not required. ``` ``` The resulting signal is in the format `{ name: string, contents: string, mime: string }[]`. See the [file upload](https://data-star.dev/examples/file_upload) example. > If you want files to be uploaded to the server, rather than be converted to signals, use a form and with `multipart/form-data` in the [`enctype`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype) attribute. See the [backend actions](https://data-star.dev/reference/actions#backend-actions) reference. #### Modifiers Modifiers allow you to modify behavior when binding signals using a key. - `__case` – Converts the casing of the signal name. - `.camel` – Camel case: `mySignal` (default) - `.kebab` – Kebab case: `my-signal` - `.snake` – Snake case: `my_signal` - `.pascal` – Pascal case: `MySignal` ``` ``` ### `data-class` Adds or removes a class to or from an element based on an expression. ```
``` If the expression evaluates to `true`, the `hidden` class is added to the element; otherwise, it is removed. The `data-class` attribute can also be used to add or remove multiple classes from an element using a set of key-value pairs, where the keys represent class names and the values represent expressions. ```
``` #### Modifiers Modifiers allow you to modify behavior when defining a class name using a key. - `__case` – Converts the casing of the class. - `.camel` – Camel case: `myClass` - `.kebab` – Kebab case: `my-class` (default) - `.snake` – Snake case: `my_class` - `.pascal` – Pascal case: `MyClass` ```
``` ### `data-computed` Creates a signal that is computed based on an expression. The computed signal is read-only, and its value is automatically updated when any signals in the expression are updated. ```
``` Computed signals are useful for memoizing expressions containing other signals. Their values can be used in other expressions. ```
``` > Computed signal expressions must not be used for performing actions (changing other signals, actions, JavaScript functions, etc.). If you need to perform an action in response to a signal change, use the [`data-effect`](#data-effect) attribute. The `data-computed` attribute can also be used to create computed signals using a set of key-value pairs, where the keys represent signal names and the values are callables (usually arrow functions) that return a reactive value. ```
``` #### Modifiers Modifiers allow you to modify behavior when defining computed signals using a key. - `__case` – Converts the casing of the signal name. - `.camel` – Camel case: `mySignal` (default) - `.kebab` – Kebab case: `my-signal` - `.snake` – Snake case: `my_signal` - `.pascal` – Pascal case: `MySignal` ```
``` ### `data-effect` Executes an expression on page load and whenever any signals in the expression change. This is useful for performing side effects, such as updating other signals, making requests to the backend, or manipulating the DOM. ```
``` ### `data-ignore` Datastar walks the entire DOM and applies plugins to each element it encounters. It’s possible to tell Datastar to ignore an element and its descendants by placing a `data-ignore` attribute on it. This can be useful for preventing naming conflicts with third-party libraries, or when you are unable to [escape user input](https://data-star.dev/reference/security#escape-user-input). ```
Datastar will not process this element.
``` #### Modifiers - `__self` – Only ignore the element itself, not its descendants. ### `data-ignore-morph` Similar to the `data-ignore` attribute, the `data-ignore-morph` attribute tells the `PatchElements` watcher to skip processing an element and its children when morphing elements. ```
This element will not be morphed.
``` > To remove the `data-ignore-morph` attribute from an element, simply patch the element with the `data-ignore-morph` attribute removed. ### `data-indicator` Creates a signal and sets its value to `true` while a fetch request is in flight, otherwise `false`. The signal can be used to show a loading indicator. ``` ``` This can be useful for showing a loading spinner, disabling a button, etc. ```
Loading...
``` The signal name can be specified in the key (as above), or in the value (as below). This can be useful depending on the templating language you are using. ``` ``` When using `data-indicator` with a fetch request initiated in a `data-init` attribute, you should ensure that the indicator signal is created before the fetch request is initialized. ```
``` #### Modifiers Modifiers allow you to modify behavior when defining indicator signals using a key. - `__case` – Converts the casing of the signal name. - `.camel` – Camel case: `mySignal` (default) - `.kebab` – Kebab case: `my-signal` - `.snake` – Snake case: `my_signal` - `.pascal` – Pascal case: `MySignal` ### `data-init` Runs an expression when the attribute is initialized. This can happen on page load, when an element is patched into the DOM, and any time the attribute is modified (via a backend action or otherwise). > The expression contained in the [`data-init`](#data-init) attribute is executed when the element attribute is loaded into the DOM. This can happen on page load, when an element is patched into the DOM, and any time the attribute is modified (via a backend action or otherwise). ```
``` #### Modifiers Modifiers allow you to add a delay to the event listener. - `__delay` – Delay the event listener. - `.500ms` – Delay for 500 milliseconds (accepts any integer). - `.1s` – Delay for 1 second (accepts any integer). - `__viewtransition` – Wraps the expression in `document.startViewTransition()` when the View Transition API is available. ```
``` ### `data-json-signals` Sets the text content of an element to a reactive JSON stringified version of signals. Useful when troubleshooting an issue. ```

```

You can optionally provide a filter object to include or exclude specific signals using regular expressions.

```








```

#### Modifiers

Modifiers allow you to modify the output format.

- `__terse` – Outputs a more compact JSON format without extra whitespace. Useful for displaying filtered data inline.

```


```

### `data-on` 

Attaches an event listener to an element, executing an expression whenever the event is triggered.

```

```

An `evt` variable that represents the event object is available in the expression.

```
``` The `data-on` attribute works with [events](https://developer.mozilla.org/en-US/docs/Web/Events) and [custom events](https://developer.mozilla.org/en-US/docs/Web/Events/Creating_and_triggering_events). The `data-on:submit` event listener prevents the default submission behavior of forms. #### Modifiers Modifiers allow you to modify behavior when events are triggered. Some modifiers have tags to further modify the behavior. - `__once` * – Only trigger the event listener once. - `__passive` * – Do not call `preventDefault` on the event listener. - `__capture` * – Use a capture event listener. - `__case` – Converts the casing of the event. - `.camel` – Camel case: `myEvent` - `.kebab` – Kebab case: `my-event` (default) - `.snake` – Snake case: `my_event` - `.pascal` – Pascal case: `MyEvent` - `__delay` – Delay the event listener. - `.500ms` – Delay for 500 milliseconds (accepts any integer). - `.1s` – Delay for 1 second (accepts any integer). - `__debounce` – Debounce the event listener. - `.500ms` – Debounce for 500 milliseconds (accepts any integer). - `.1s` – Debounce for 1 second (accepts any integer). - `.leading` – Debounce with leading edge (must come after timing). - `.notrailing` – Debounce without trailing edge (must come after timing). - `__throttle` – Throttle the event listener. - `.500ms` – Throttle for 500 milliseconds (accepts any integer). - `.1s` – Throttle for 1 second (accepts any integer). - `.noleading` – Throttle without leading edge (must come after timing). - `.trailing` – Throttle with trailing edge (must come after timing). - `__viewtransition` – Wraps the expression in `document.startViewTransition()` when the View Transition API is available. - `__window` – Attaches the event listener to the `window` element. - `__outside` – Triggers when the event is outside the element. - `__prevent` – Calls `preventDefault` on the event listener. - `__stop` – Calls `stopPropagation` on the event listener. ** Only works with built-in events.* ```
``` ### `data-on-intersect` Runs an expression when the element intersects with the viewport. ```
``` #### Modifiers Modifiers allow you to modify the element intersection behavior and the timing of the event listener. - `__once` – Only triggers the event once. - `__exit` – Only triggers the event when the element exits the viewport. - `__half` – Triggers when half of the element is visible. - `__full` – Triggers when the full element is visible. - `__threshold` – Triggers when the element is visible by a certain percentage. - `.25` – Triggers when 25% of the element is visible. - `.75` – Triggers when 75% of the element is visible. - `__delay` – Delay the event listener. - `.500ms` – Delay for 500 milliseconds (accepts any integer). - `.1s` – Delay for 1 second (accepts any integer). - `__debounce` – Debounce the event listener. - `.500ms` – Debounce for 500 milliseconds (accepts any integer). - `.1s` – Debounce for 1 second (accepts any integer). - `.leading` – Debounce with leading edge (must come after timing). - `.notrailing` – Debounce without trailing edge (must come after timing). - `__throttle` – Throttle the event listener. - `.500ms` – Throttle for 500 milliseconds (accepts any integer). - `.1s` – Throttle for 1 second (accepts any integer). - `.noleading` – Throttle without leading edge (must come after timing). - `.trailing` – Throttle with trailing edge (must come after timing). - `__viewtransition` – Wraps the expression in `document.startViewTransition()` when the View Transition API is available. ```
``` ### `data-on-interval` Runs an expression at a regular interval. The interval duration defaults to one second and can be modified using the `__duration` modifier. ```
``` #### Modifiers Modifiers allow you to modify the interval duration. - `__duration` – Sets the interval duration. - `.500ms` – Interval duration of 500 milliseconds (accepts any integer). - `.1s` – Interval duration of 1 second (default). - `.leading` – Execute the first interval immediately. - `__viewtransition` – Wraps the expression in `document.startViewTransition()` when the View Transition API is available. ```
``` ### `data-on-signal-patch` Runs an expression whenever any signals are patched. This is useful for tracking changes, updating computed values, or triggering side effects when data updates. ```
``` The `patch` variable is available in the expression and contains the signal patch details. ```
``` You can filter which signals to watch using the [`data-on-signal-patch-filter`](#data-on-signal-patch-filter) attribute. #### Modifiers Modifiers allow you to modify the timing of the event listener. - `__delay` – Delay the event listener. - `.500ms` – Delay for 500 milliseconds (accepts any integer). - `.1s` – Delay for 1 second (accepts any integer). - `__debounce` – Debounce the event listener. - `.500ms` – Debounce for 500 milliseconds (accepts any integer). - `.1s` – Debounce for 1 second (accepts any integer). - `.leading` – Debounce with leading edge (must come after timing). - `.notrailing` – Debounce without trailing edge (must come after timing). - `__throttle` – Throttle the event listener. - `.500ms` – Throttle for 500 milliseconds (accepts any integer). - `.1s` – Throttle for 1 second (accepts any integer). - `.noleading` – Throttle without leading edge (must come after timing). - `.trailing` – Throttle with trailing edge (must come after timing). ```
``` ### `data-on-signal-patch-filter` Filters which signals to watch when using the [`data-on-signal-patch`](#data-on-signal-patch) attribute. The `data-on-signal-patch-filter` attribute accepts an object with `include` and/or `exclude` properties that are regular expressions. ```
``` ### `data-preserve-attr` Preserves the value of an attribute when morphing DOM elements. ```
Title Content
``` You can preserve multiple attributes by separating them with a space. ```
Title Content
``` ### `data-ref` Creates a new signal that is a reference to the element on which the data attribute is placed. ```
``` The signal name can be specified in the key (as above), or in the value (as below). This can be useful depending on the templating language you are using. ```
``` The signal value can then be used to reference the element. ``` $foo is a reference to a element ``` #### Modifiers Modifiers allow you to modify behavior when defining references using a key. - `__case` – Converts the casing of the signal name. - `.camel` – Camel case: `mySignal` (default) - `.kebab` – Kebab case: `my-signal` - `.snake` – Snake case: `my_signal` - `.pascal` – Pascal case: `MySignal` ```
``` ### `data-show` Shows or hides an element based on whether an expression evaluates to `true` or `false`. For anything with custom requirements, use [`data-class`](#data-class) instead. ```
``` To prevent flickering of the element before Datastar has processed the DOM, you can add a `display: none` style to the element to hide it initially. ```
``` ### `data-signals` Patches (adds, updates or removes) one or more signals into the existing signals. Values defined later in the DOM tree override those defined earlier. ```
``` Signals can be nested using dot-notation. ```
``` The `data-signals` attribute can also be used to patch multiple signals using a set of key-value pairs, where the keys represent signal names and the values represent expressions. ```
``` The value above is written in JavaScript object notation, but JSON, which is a subset and which most templating languages have built-in support for, is also allowed. Setting a signal’s value to `null` or `undefined` removes the signal. ```
``` Keys used in `data-signals:*` are converted to camel case, so the signal name `mySignal` must be written as `data-signals:my-signal` or `data-signals="{mySignal: 1}"`. Signals beginning with an underscore are *not* included in requests to the backend by default. You can opt to include them by modifying the value of the [`filterSignals`](https://data-star.dev/reference/actions#filterSignals) option. > Signal names cannot begin with nor contain a double underscore (`__`), due to its use as a modifier delimiter. #### Modifiers Modifiers allow you to modify behavior when patching signals using a key. - `__case` – Converts the casing of the signal name. - `.camel` – Camel case: `mySignal` (default) - `.kebab` – Kebab case: `my-signal` - `.snake` – Snake case: `my_signal` - `.pascal` – Pascal case: `MySignal` - `__ifmissing` – Only patches signals if their keys do not already exist. This is useful for setting defaults without overwriting existing values. ```
``` ### `data-style` Sets the value of inline CSS styles on an element based on an expression, and keeps them in sync. ```
``` The `data-style` attribute can also be used to set multiple style properties on an element using a set of key-value pairs, where the keys represent CSS property names and the values represent expressions. ```
``` Empty string, `null`, `undefined`, or `false` values will restore the original inline style value if one existed, or remove the style property if there was no initial value. This allows you to use the logical AND operator (`&&`) for conditional styles: `$condition && 'value'` will apply the style when the condition is true and restore the original value when false. ```
``` The plugin tracks initial inline style values and restores them when data-style expressions become falsy or during cleanup. This ensures existing inline styles are preserved and only the dynamic changes are managed by Datastar. ### `data-text` Binds the text content of an element to an expression. ```
``` ## Pro Attributes The Pro attributes add functionality to the free open source Datastar framework. These attributes are available under a [commercial license](https://data-star.dev/datastar_pro#license) that helps fund our open source work. ### `data-animate` [Pro](https://data-star.dev/datastar_pro "Datastar Pro") Allows you to animate element attributes over time. Animated attributes are updated reactively whenever signals used in the expression change. ### `data-custom-validity` [Pro](https://data-star.dev/datastar_pro "Datastar Pro") Allows you to add custom validity to an element using an expression. The expression must evaluate to a string that will be set as the custom validity message. If the string is empty, the input is considered valid. If the string is non-empty, the input is considered invalid and the string is used as the reported message. ```
``` ### `data-on-raf` [Pro](https://data-star.dev/datastar_pro "Datastar Pro") Runs an expression on every [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame) event. ```
``` #### Modifiers Modifiers allow you to modify the timing of the event listener. - `__throttle` – Throttle the event listener. - `.500ms` – Throttle for 500 milliseconds (accepts any integer). - `.1s` – Throttle for 1 second (accepts any integer). - `.noleading` – Throttle without leading edge (must come after timing). - `.trailing` – Throttle with trailing edge (must come after timing). ```
``` ### `data-on-resize` [Pro](https://data-star.dev/datastar_pro "Datastar Pro") Runs an expression whenever an element’s dimensions change. ```
``` #### Modifiers Modifiers allow you to modify the timing of the event listener. - `__debounce` – Debounce the event listener. - `.500ms` – Debounce for 500 milliseconds (accepts any integer). - `.1s` – Debounce for 1 second (accepts any integer). - `.leading` – Debounce with leading edge (must come after timing). - `.notrailing` – Debounce without trailing edge (must come after timing). - `__throttle` – Throttle the event listener. - `.500ms` – Throttle for 500 milliseconds (accepts any integer). - `.1s` – Throttle for 1 second (accepts any integer). - `.noleading` – Throttle without leading edge (must come after timing). - `.trailing` – Throttle with trailing edge (must come after timing). ```
``` ### `data-persist` [Pro](https://data-star.dev/datastar_pro "Datastar Pro") Persists signals in [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). This is useful for storing values between page loads. ```
``` The signals to be persisted can be filtered by providing a value that is an object with `include` and/or `exclude` properties that are regular expressions. ```
``` You can use a custom storage key by adding it after `data-persist:`. By default, signals are stored using the key `datastar`. ```
``` #### Modifiers Modifiers allow you to modify the storage target. - `__session` – Persists signals in [session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) instead of local storage. ```
``` ### `data-query-string` [Pro](https://data-star.dev/datastar_pro "Datastar Pro") Syncs query string params to signal values on page load, and syncs signal values to query string params on change. ```
``` The signals to be synced can be filtered by providing a value that is an object with `include` and/or `exclude` properties that are regular expressions. ```
``` #### Modifiers Modifiers allow you to enable history support. - `__filter` – Filters out empty values when syncing signal values to query string params. - `__history` – Enables history support – each time a matching signal changes, a new entry is added to the browser’s history stack. Signal values are restored from the query string params on popstate events. ```
``` ### `data-replace-url` [Pro](https://data-star.dev/datastar_pro "Datastar Pro") Replaces the URL in the browser without reloading the page. The value can be a relative or absolute URL, and is an evaluated expression. ```
``` ### `data-scroll-into-view` [Pro](https://data-star.dev/datastar_pro "Datastar Pro") Scrolls the element into view. Useful when updating the DOM from the backend, and you want to scroll to the new content. ```
``` #### Modifiers Modifiers allow you to modify scrolling behavior. - `__smooth` – Scrolling is animated smoothly. - `__instant` – Scrolling is instant. - `__auto` – Scrolling is determined by the computed `scroll-behavior` CSS property. - `__hstart` – Scrolls to the left of the element. - `__hcenter` – Scrolls to the horizontal center of the element. - `__hend` – Scrolls to the right of the element. - `__hnearest` – Scrolls to the nearest horizontal edge of the element. - `__vstart` – Scrolls to the top of the element. - `__vcenter` – Scrolls to the vertical center of the element. - `__vend` – Scrolls to the bottom of the element. - `__vnearest` – Scrolls to the nearest vertical edge of the element. - `__focus` – Focuses the element after scrolling. ### `data-rocket` [Pro](https://data-star.dev/datastar_pro "Datastar Pro") Creates a Rocket web component. See the [Rocket reference](https://data-star.dev/reference/rocket) for details. ### `data-view-transition` [Pro](https://data-star.dev/datastar_pro "Datastar Pro") Sets the `view-transition-name` style attribute explicitly. ```
``` Page level transitions are automatically handled by an injected meta tag. Inter-page elements are automatically transitioned if the [View Transition API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) is available in the browser and `useViewTransitions` is `true`. ## Attribute Evaluation Order Elements are evaluated by walking the DOM in a depth-first manner, and attributes are applied in the order they appear in the element. This is important in some cases, such as when using `data-indicator` with a fetch request initiated in a `data-init` attribute, in which the indicator signal must be created before the fetch request is initialized. ```
``` Data attributes are evaluated and applied on page load (after Datastar has initialized), and are reapplied after any DOM patches that add, remove, or change them. Note that [morphing elements](https://data-star.dev/reference/sse_events#datastar-patch-elements) preserves existing attributes unless they are explicitly changed in the DOM, meaning they will only be reapplied if the attribute itself is changed. ## Attribute Casing [According to the HTML spec](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*), all `data-*` attributes (not Datastar the framework, but any time a data attribute appears in the DOM) are case-insensitive. When Datastar processes these attributes, hyphenated names are automatically converted to [camel case](https://developer.mozilla.org/en-US/docs/Glossary/Camel_case) by removing hyphens and uppercasing the letter following each hyphen. Datastar handles casing of data attribute key suffixes containing hyphens in two ways: . The keys used in attributes that define signals (`data-bind:*`, `data-signals:*`, `data-computed:*`, etc.), are converted to camel case (the recommended casing for signals) by removing hyphens and uppercasing the letter following each hyphen. For example, `data-signals:my-signal` defines a signal named `mySignal`, and you would use the signal in a [Datastar expression](https://data-star.dev/guide/datastar_expressions) as `$mySignal`. . The keys suffixes used by all other attributes are, by default, converted to [kebab case](https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case). For example, `data-class:text-blue-700` adds or removes the class `text-blue-700`, and `data-on:rocket-launched` would react to the event named `rocket-launched`. You can use the `__case` modifier to convert between `camelCase`, `kebab-case`, `snake_case`, and `PascalCase`, or alternatively use object syntax when available. For example, if listening for an event called `widgetLoaded`, you would use `data-on:widget-loaded__case.camel`. ## Aliasing Attributes It is possible to alias `data-*` attributes to a custom alias (`data-alias-*`, for example) using the [bundler](https://data-star.dev/bundler). A custom alias should *only* be used if you have a conflict with a legacy library and [`data-ignore`](#data-ignore) cannot be used. We maintain a `data-star-*` aliased version that can be included as follows. ``` ``` ## Datastar Expressions Datastar expressions used in `data-*` attributes parse signals, converting all dollar signs followed by valid signal name characters into their corresponding signal values. Expressions support standard JavaScript syntax, including operators, function calls, ternary expressions, and object and array literals. A variable `el` is available in every Datastar expression, representing the element that the attribute exists on. ```
``` Read more about [Datastar expressions](https://data-star.dev/guide/datastar_expressions) in the guide. ## Error Handling Datastar has built-in error handling and reporting for runtime errors. When a data attribute is used incorrectly, for example `data-text-foo`, the following error message is logged to the browser console. ``` Uncaught datastar runtime error: textKeyNotAllowed More info: https://data-star.dev/errors/key_not_allowed?metadata=%7B%22plugin%22%3A%7B%22name%22%3A%22text%22%2C%22type%22%3A%22attribute%22%7D%2C%22element%22%3A%7B%22id%22%3A%22%22%2C%22tag%22%3A%22DIV%22%7D%2C%22expression%22%3A%7B%22rawKey%22%3A%22textFoo%22%2C%22key%22%3A%22foo%22%2C%22value%22%3A%22%22%2C%22fnContent%22%3A%22%22%7D%7D Context: { "plugin": { "name": "text", "type": "attribute" }, "element": { "id": "", "tag": "DIV" }, "expression": { "rawKey": "textFoo", "key": "foo", "value": "", "fnContent": "" } } ``` The “More info” link takes you directly to a context-aware error page that explains the error and provides correct sample usage. See [the error page for the example above](https://data-star.dev/errors/key_not_allowed?metadata=%7B%22plugin%22%3A%7B%22name%22%3A%22text%22%2C%22type%22%3A%22attribute%22%7D%2C%22element%22%3A%7B%22id%22%3A%22%22%2C%22tag%22%3A%22DIV%22%7D%2C%22expression%22%3A%7B%22rawKey%22%3A%22textFoo%22%2C%22key%22%3A%22foo%22%2C%22value%22%3A%22%22%2C%22fnContent%22%3A%22%22%7D%7D), and all available error messages in the sidebar menu. ### Actions Datastar provides actions (helper functions) that can be used in Datastar expressions. > The `@` prefix designates actions that are safe to use in expressions. This is a security feature that prevents arbitrary JavaScript from being executed in the browser. Datastar uses [`Function()` constructors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) to create and execute these actions in a secure and controlled sandboxed environment. ### `@peek()` > `@peek(callable: () => any)` Allows accessing signals without subscribing to their changes in expressions. ```
``` In the example above, the expression in the `data-text` attribute will be re-evaluated whenever `$foo` changes, but it will *not* be re-evaluated when `$bar` changes, since it is evaluated inside the `@peek()` action. ### `@setAll()` > `@setAll(value: any, filter?: {include: RegExp, exclude?: RegExp})` Sets the value of all matching signals (or all signals if no filter is used) to the expression provided in the first argument. The second argument is an optional filter object with an `include` property that accepts a regular expression to match signal paths. You can optionally provide an `exclude` property to exclude specific patterns. > The [Datastar Inspector](https://data-star.dev/datastar_pro#datastar-inspector) can be used to inspect and filter current signals and view signal patch events in real-time. ```
``` ### `@toggleAll()` > `@toggleAll(filter?: {include: RegExp, exclude?: RegExp})` Toggles the boolean value of all matching signals (or all signals if no filter is used). The argument is an optional filter object with an `include` property that accepts a regular expression to match signal paths. You can optionally provide an `exclude` property to exclude specific patterns. > The [Datastar Inspector](https://data-star.dev/datastar_pro#datastar-inspector) can be used to inspect and filter current signals and view signal patch events in real-time. ```
``` ## Backend Actions ### `@get()` > `@get(uri: string, options={ })` Sends a `GET` request to the backend using the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). The URI can be any valid endpoint and the response must contain zero or more [Datastar SSE events](https://data-star.dev/reference/sse_events). ``` ``` By default, requests are sent with a `Datastar-Request: true` header, and a `{datastar: *}` object containing all existing signals, except those beginning with an underscore. This behavior can be changed using the [`filterSignals`](#filterSignals) option, which allows you to include or exclude specific signals using regular expressions. > When using a `get` request, the signals are sent as a query parameter, otherwise they are sent as a JSON body. When a page is hidden (in a background tab, for example), the default behavior for `get` requests is for the SSE connection to be closed, and reopened when the page becomes visible again. To keep the connection open when the page is hidden, set the [`openWhenHidden`](#openWhenHidden) option to `true`. ``` ``` It’s possible to send form encoded requests by setting the `contentType` option to `form`. This sends requests using `application/x-www-form-urlencoded` encoding. ``` ``` It’s also possible to send requests using `multipart/form-data` encoding by specifying it in the `form` element’s [`enctype`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype) attribute. This should be used when uploading files. See the [form data example](https://data-star.dev/examples/form_data). ```
``` ### `@post()` > `@post(uri: string, options={ })` Works the same as [`@get()`](#get) but sends a `POST` request to the backend. ``` ``` ### `@put()` > `@put(uri: string, options={ })` Works the same as [`@get()`](#get) but sends a `PUT` request to the backend. ``` ``` ### `@patch()` > `@patch(uri: string, options={ })` Works the same as [`@get()`](#get) but sends a `PATCH` request to the backend. ``` ``` ### `@delete()` > `@delete(uri: string, options={ })` Works the same as [`@get()`](#get) but sends a `DELETE` request to the backend. ``` ``` ### Options All of the actions above take a second argument of options. - `contentType` – The type of content to send. A value of `json` sends all signals in a JSON request. A value of `form` tells the action to look for the closest form to the element on which it is placed (unless a `selector` option is provided), perform validation on the form elements, and send them to the backend using a form request (no signals are sent). Defaults to `json`. - `filterSignals` – A filter object with an `include` property that accepts a regular expression to match signal paths (defaults to all signals: `/.*/`), and an optional `exclude` property to exclude specific signal paths (defaults to all signals that do not have a `_` prefix: `/(^_|\._).*/`). > The [Datastar Inspector](https://data-star.dev/datastar_pro#datastar-inspector) can be used to inspect and filter current signals and view signal patch events in real-time. - `selector` – Optionally specifies a form to send when the `contentType` option is set to `form`. If the value is `null`, the closest form is used. Defaults to `null`. - `headers` – An object containing headers to send with the request. - `openWhenHidden` – Whether to keep the connection open when the page is hidden. Useful for dashboards but can cause a drain on battery life and other resources when enabled. Defaults to `false` for `get` requests, and `true` for all other HTTP methods. - `payload` – Allows the fetch payload to be overridden with a custom object. - `retry` – Determines when to retry requests. Can be `'auto'` (default, retries on network errors only), `'error'` (retries on `4xx` and `5xx` responses), `'always'` (retries on all non-`204` responses except redirects), or `'never'` (disables retries). Defaults to `'auto'`. - `retryInterval` – The retry interval in milliseconds. Defaults to `1000` (one second). - `retryScaler` – A numeric multiplier applied to scale retry wait times. Defaults to `2`. - `retryMaxWaitMs` – The maximum allowable wait time in milliseconds between retries. Defaults to `30000` (30 seconds). - `retryMaxCount` – The maximum number of retry attempts. Defaults to `10`. - `requestCancellation` – Controls request cancellation behavior. Can be `'auto'` (default, cancels existing requests on the same element), `'disabled'` (allows concurrent requests), or an `AbortController` instance for custom control. Defaults to `'auto'`. ``` ``` ### Request Cancellation By default, when a new fetch request is initiated on an element, any existing request on that same element is automatically cancelled. This prevents multiple concurrent requests from conflicting with each other and ensures clean state management. For example, if a user rapidly clicks a button that triggers a backend action, only the most recent request will be processed: ``` ``` This automatic cancellation happens at the element level, meaning requests on different elements can run concurrently without interfering with each other. You can control this behavior using the [`requestCancellation`](#requestCancellation) option: ```
``` ### Response Handling Backend actions automatically handle different response content types: - `text/event-stream` – Standard SSE responses with [Datastar SSE events](https://data-star.dev/reference/sse_events). - `text/html` – HTML elements to patch into the DOM. - `application/json` – JSON encoded signals to patch. - `text/javascript` – JavaScript code to execute in the browser. #### `text/html` When returning HTML (`text/html`), the server can optionally include the following response headers: - `datastar-selector` – A CSS selector for the target elements to patch - `datastar-mode` – How to patch the elements (`outer`, `inner`, `remove`, `replace`, `prepend`, `append`, `before`, `after`). Defaults to `outer`. - `datastar-use-view-transition` – Whether to use the [View Transition API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) when patching elements. ``` response.headers.set('Content-Type', 'text/html') response.headers.set('datastar-selector', '#my-element') response.headers.set('datastar-mode', 'inner') response.body = '

New content

' ``` #### `application/json` When returning JSON (`application/json`), the server can optionally include the following response header: - `datastar-only-if-missing` – If set to `true`, only patch signals that don’t already exist. ``` response.headers.set('Content-Type', 'application/json') response.headers.set('datastar-only-if-missing', 'true') response.body = JSON.stringify({ foo: 'bar' }) ``` #### `text/javascript` When returning JavaScript (`text/javascript`), the server can optionally include the following response header: - `datastar-script-attributes` – Sets the script element’s attributes using a JSON encoded string. ``` response.headers.set('Content-Type', 'text/javascript') response.headers.set('datastar-script-attributes', JSON.stringify({ type: 'module' })) response.body = 'console.log("Hello from server!");' ``` ### Events All of the actions above trigger `datastar-fetch` events during the fetch request lifecycle. The event type determines the stage of the request. - `started` – Triggered when the fetch request is started. - `finished` – Triggered when the fetch request is finished. - `error` – Triggered when the fetch request encounters an error. - `retrying` – Triggered when the fetch request is retrying. - `retries-failed` – Triggered when all fetch retries have failed. ```
``` ## Pro Actions ### `@clipboard()` [Pro](https://data-star.dev/datastar_pro "Datastar Pro") > `@clipboard(text: string, isBase64?: boolean)` Copies the provided text to the clipboard. If the second parameter is `true`, the text is treated as [Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64) encoded, and is decoded before copying. > Base64 encoding is useful when copying content that contains special characters, quotes, or code fragments that might not be valid within HTML attributes. This prevents parsing errors and ensures the content is safely embedded in `data-*` attributes. ``` ``` ### `@fit()` [Pro](https://data-star.dev/datastar_pro "Datastar Pro") > `@fit(v: number, oldMin: number, oldMax: number, newMin: number, newMax: number, shouldClamp=false, shouldRound=false)` Linearly interpolates a value from one range to another. This is useful for converting between different scales, such as mapping a slider value to a percentage or converting temperature units. The optional `shouldClamp` parameter ensures the result stays within the new range, and `shouldRound` rounds the result to the nearest integer. ```
RGB Value:
°C = °F
Move your mouse horizontally to change opacity
``` ### Rocket Rocket is currently in alpha – available in the Datastar Pro repo. Rocket is a [Datastar Pro](https://data-star.dev/datastar_pro) plugin that bridges [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) with Datastar’s reactive system. It allows you to create encapsulated, reusable components with reactive data binding. > Rocket is a powerful feature, and should be used sparingly. For most applications, standard Datastar templates and global signals are sufficient. Reserve Rocket for cases where component encapsulation is essential, such as integrating third-party libraries or creating complex, reusable UI elements. ### Basic example Traditional web components require verbose class definitions and manual DOM management. Rocket eliminates this complexity with a declarative, template-based approach. Here’s a Rocket component compared to a vanilla web component. ![](https://data-star.dev/cdn-cgi/image/format=auto/static/images/rocket-128x128-38eb37d4251e4854ef5fdd7bbee042336c82e36fa2fbafdd212d911c74c7fd39.png) ``` ``` ``` class SimpleCounter extends HTMLElement { static observedAttributes = ['start', 'step']; constructor() { super(); this.innerHTML = ` 0 `; this.errorEl = this.querySelector('.error'); this.decBtn = this.querySelector('.dec'); this.incBtn = this.querySelector('.inc'); this.resetBtn = this.querySelector('.reset'); this.countEl = this.querySelector('.count'); this.handleDec = () => { const newValue = this.count - this.step; if (newValue >= 0) { this.count = newValue; this.updateDisplay(); } }; this.handleInc = () => { this.count += this.step; this.updateDisplay(); }; this.handleReset = () => { this.count = this.start; this.updateDisplay(); }; this.decBtn.addEventListener('click', this.handleDec); this.incBtn.addEventListener('click', this.handleInc); this.resetBtn.addEventListener('click', this.handleReset); } connectedCallback() { const startVal = parseInt(this.getAttribute('start') || '0'); const stepVal = parseInt(this.getAttribute('step') || '1'); if (startVal < 0) { this.errorEl.textContent = 'start must be at least 0'; this.errorEl.style.display = 'block'; this.start = 0; } else { this.start = startVal; this.errorEl.style.display = 'none'; } if (stepVal < 1 || stepVal > 10) { this.errorEl.textContent = 'step must be between 1 and 10'; this.errorEl.style.display = 'block'; this.step = Math.max(1, Math.min(10, stepVal)); } else { this.step = stepVal; if (this.start === startVal) { this.errorEl.style.display = 'none'; } } this.count = this.start; this.updateDisplay(); } disconnectedCallback() { this.decBtn.removeEventListener('click', this.handleDec); this.incBtn.removeEventListener('click', this.handleInc); this.resetBtn.removeEventListener('click', this.handleReset); } attributeChangedCallback(name, oldValue, newValue) { if (name === 'start') { const startVal = parseInt(newValue || '0'); if (startVal < 0) { this.errorEl.textContent = 'start must be at least 0'; this.errorEl.style.display = 'block'; this.start = 0; } else { this.start = startVal; this.errorEl.style.display = 'none'; } this.count = this.start; } else if (name === 'step') { const stepVal = parseInt(newValue || '1'); if (stepVal < 1 || stepVal > 10) { this.errorEl.textContent = 'step must be between 1 and 10'; this.errorEl.style.display = 'block'; this.step = Math.max(1, Math.min(10, stepVal)); } else { this.step = stepVal; this.errorEl.style.display = 'none'; } } if (this.isConnected) { this.updateDisplay(); } } updateDisplay() { this.countEl.textContent = this.count; } } customElements.define('simple-counter', SimpleCounter); ``` ## Overview Rocket allows you to turn HTML templates into fully reactive web components. The backend remains the source of truth, but your frontend components are now encapsulated and reusable without any of the usual hassle. Add `data-rocket:my-component` to a `template` element to turn it into a Rocket component. Component signals are automatically [scoped](#signal-scoping) with `$$`, so component instances don’t interfere with each other. You can use Rocket to wrap external libraries using [module imports](#module-imports), and create [references to elements](#element-references) within your component. Each component gets its own signal namespace that plays nicely with Datastar’s global signals. When you remove a component from the DOM, all its `$$` signals are cleaned up automatically. ### Bridging Web Components and Datastar Web components want encapsulation; Datastar wants a global signal store. Rocket gives you both by creating isolated namespaces for each component. Each instance gets its own sandbox that doesn’t mess with other components on the page, or with global signals. Multiple component instances work seamlessly, each getting its own numbered namespace. You still have access to global signals when you need them, but your component state stays isolated and clean. ### Signal Scoping Use `$$` for component-scoped signals, and `$` for global signals. Component signals are automatically cleaned up when you remove the component from the DOM - no memory leaks, no manual cleanup required. Behind the scenes, your `$$count` becomes something like `$._rocket.my_counter.id1.count`, with each instance getting its own id-prefixed namespace. You never have to think about this complexity - just write `$$count` and Rocket handles the rest. ``` // Your component template writes: // Rocket transforms it to (for instance #1): // The global Datastar signal structure: $._rocket = { my_counter: { id1: { count: 0 }, // First counter instance id2: { count: 5 }, // Second counter instance id3: { count: 10 } // Third counter instance }, user_card: { id4: { name: "Alice" }, // Different component type id5: { name: "Bob" } } } ``` ## Defining Rocket Components Rocket components are defined using a HTML `template` element with the `data-rocket:my-component` attribute, where `my-component` is the name of the resulting web component. The name must contain at least one hyphen, as per the [custom element](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#name) specification. ``` ``` This gets compiled to a web component, meaning that usage is simply: ``` ``` Rocket components *must* be defined before being used in the DOM. ``` ``` ## Signal Management Rocket makes it possible to work with both component-scoped and global signals (global to the entire page). ### Component Signals Component-scoped signals use the `$$` prefix and are isolated to each component instance. ``` ``` ### Global Signals Global signals use the `$` prefix and are shared across the entire page. ``` ``` ## Props The `data-props:*` attribute allows you to define component props with codecs for validation and defaults. ``` ``` Rocket automatically transforms and validates values using the [codecs](#validation-with-codecs) defined in `data-props:*` attributes. ## Setup Scripts Setup scripts initialize component behavior and run when the component is created. Rocket supports both component (per-instance) and static (one-time) setup scripts. ### Component Setup Scripts Regular `

``` ### Host Element Access Rocket injects an `el` binding into every component setup script. It always points to the current custom element instance, even when you opt into Shadow DOM, so you can imperatively read attributes, toggle classes, or wire event listeners. ``` ``` Setup code executes inside an arrow function sandbox, so `this` has no meaning inside component scripts. Use `el` any time you need the host element—for example to call `el.shadowRoot`, `el.setAttribute`, or pass it into a third-party library. ### Static Setup Scripts Scripts with a `data-static` attribute only run once, when the component type is first registered. This is useful for shared constants or utilities. ``` ``` ## Module Imports Rocket allows you to wrap external libraries, loading them before the component initializes and the setup script runs. Use `data-import:*` for modern ES modules, and add the `__iife` modifier (`data-import:foo__iife`) for legacy globals. ### ESM Imports The `data-import:*` attribute loads modern ES modules by default. ``` ``` ### IIFE Imports Add the `__iife` modifier for legacy libraries that expose globals. The library must expose a global variable that matches the alias you specify after `data-import:`. ``` ``` ## Rocket Attributes In addition to the Rocket-specific `data-*` attributes defined above, the following attributes are available within Rocket components. Rocket only transforms Datastar attributes such as `data-text`, `data-on`, and `data-attr`. Custom `data-*` attributes you add for your own semantics (e.g., `data-info="Hello Delaney!"`) are preserved verbatim in the rendered DOM. By default, Rocket renders into the light DOM of the custom element, so the component’s content participates directly in the page layout and inherits global styles. The shadow attributes `data-shadow-*` let's you opt a component into using a Shadow DOM host instead. If you’re not familiar with Shadow DOM concepts like the [shadow root](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot), it’s worth reading the MDN documentation first. ### Light DOM style scoping Light DOM Rocket components automatically scope any `
Alpha ``` ### `data-shadow-open` Use `data-shadow-open` to force an **open Shadow DOM** when you want style encapsulation but still need access to internal elements via `element.shadowRoot`, which is useful during debugging or integration. ``` ``` ### `data-shadow-closed` Use `data-shadow-closed` to force a **closed Shadow DOM**. Choose this when you want the implementation to be fully encapsulated and inaccessible via `element.shadowRoot`, while still benefitting from Shadow DOM styling and slot projection. ``` Hover me ``` ### `data-if` Conditionally outputs an element based on an expression. Must be placed on a `