Von Deepa

Javascript Fatigue

Web development has become a technical occultism activity, in which the focus is on JavaScript and the capabilities of making HTML content dynamic instead of making a better job of delivering HTML. This JavaScript religion has led to the rise of frontend frameworks such as React, Vue, Svelte, Solid, etc. In turn those were not enough for full application and progress led the industry to full-stack JavaScript frameworks like Next.js, Nuxt, Svelte and Solid Start. Once you need a framework for reactivity it makes sense to embrace it in the backend too for consistency.

In reality almost all frameworks come down to updating the DOM as fast and as simply as possible with some effort around improving developer experience.


  • Be declarative
  • Use signals
  • Supply a set of plugins that handle 99% of problems

Datastar started as just a plugin framework but found that by having no overlap in features, it was possible to replace any SPA framework and even hypermedia focused libraries like HTMX while being much smaller and (we think) easier to use.

With Datastar, even if you have never coded before, with a few examples, you can easily create high interconnected web assets. It doesn’t matter if you are a making a user interface for bank or a simple blog. The approach is simplicity through declarative HTML.

If Datastar doesn’t match your needs, you still might be interested in using it as originally intended and write your own library.

Time to think declaratively

Declarative code is amazing.

FROM orders
WHERE state='completed'

This is SQL. As a user you don’t have to know how the query will get executed, its up to the engine used. Sure you can read the query plan or introspect indexes but you can start at a high level and drill down only when necessary.

HTML work in a similar fashion. You don’t have to worry about how a <div>Hello</div> turned into pixels nor how the page uses resources when you tab away. This is wonderful for the majority of cases but at least in modern HTML is a bit limiting.

For example:

  <label>HELLO WORLD</label>
  <input value="hello world" />

if you want to use the result of changing the input to modify the label you have to write code. What if we could extend HTML to make that process declarative? Well funny you ask…

Custom data attributes

Luckily HTML5 has the concept of data attributes which allows anyone to add thier own attributes and still be valid HTML. The only real requirement is they be kebab-cased and start with data-*. Data star… that’d be a clever name… oh I get it!

Before we can actually use the data-* we need a quick aside about signals.


Signals are a way to do fine grain reactivity in a very efficient way. They are similar to formulas in Excel. Instead of doing something like:

let a = 2;
let b = 2;
let c = a * b;

You can do:

const a = signal(2);
const b = signal(2);
const c = computed(() => a() * b());
effect(() => {

The difference is if you change a or b, c will auto schedule for updates. So you spend your time declaring relationships more than procedures. They have been popularized by Solid,js but are now used by many frameworks.

The Store

Ok so back to our hypothetical framework let’s have a way to declare stuff that can setup signals on the page using data-* attributes.

<div data-store="{value:'hello world'}"></div>

The contents is just a set of data that can evaluated by JSON.parse and will convert this data into a tree of signals and merge into a store that tracks all of the reactivity on the page. In this case we want there to be a a single value signal with the contents of 'hello world'.


Let’s replace the hard coded value in the input with another attribute

<div data-store="{value:'hello world'}">
  <label>HELLO WORLD</label>
  <input data-model="value" />

Here we’ve created a new attribute data-model with the contents of value. We are just saying when the signal value changes or input is edited on the page make sure you keep them in sync. We don’t care how, just do it.


Now we want to update the label relationship.

<div data-store="{value:'hello world'}">
  <label data-text="$value"></label>
  <input data-model="value" />

Here we’ve added another attribute data-text but the content has a $ prefix. Remember that data-* attributes are just strings, which means we can give any semantics we want as long as it’s consistent. It this case we are designating the use of a signal by adding a $ prefix. Now went the value signal updates, so will the innerHTML of the label. Pretty neat.

However it doesn’t yet match the original intent, which was to make it uppercase, so let’s make a quick adjustment.

<div data-store="{value:'hello world'}">
  <label data-text="$value.toUpperCase()"></label>
  <input data-model="value" />

So with this change and in a declarative nature you’d be able extend HTML and focus on relationships.

Our HTML looks pretty neat but what would it take to make it actually work? Well just add:

<script src=""></script>

somewhere on your page and it should just work. If you look at the dev tools of your browser you’ll also see its tiny compared to any other full framework and we haven’t scratched the surface of what’s included.

So far looking a lot like Alpine.js which is great, but as we add more features will diverge.