DEV Community

Eugene Kuzmenko
Eugene Kuzmenko

Posted on

HTMX vs KEML: The Loading State Example That Shows the Difference

A while ago, I wrote about KEML.

It didn’t land the way I wanted it to.

Not because the idea was bad — but because the examples were.
I showed things that tools like HTMX already do very well:
send a request on click, render a response, repeat.

And if that’s all you see, then yeah — KEML just looks like different syntax.

This time, I want to show something that actually feels different.


The smallest interesting example: loading state

Let’s start with a very real UI problem:

Show a loading indicator while a request is in progress.

The HTMX way

<button 
  hx-get="/data"
  hx-target="#result"
  hx-indicator="#spinner"
>
  Load Data
</button>

<div id="spinner" class="htmx-indicator">
  Loading...
</div>

<div id="result"></div>
Enter fullscreen mode Exit fullscreen mode
.htmx-indicator {
  display: none;
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
  display: block;
}
Enter fullscreen mode Exit fullscreen mode

This works well. It’s concise. It’s idiomatic.

But notice what’s happening:

  • the button references the spinner (#spinner)
  • the button references the result (#result)
  • loading is a special concept (hx-indicator)
  • visibility is controlled indirectly via CSS

Nothing here is wrong. But everything is connected.


The same thing in KEML

<button
  on:click="send"
  on="send"
  get="/data"
  result="response"
  if:loading="spinner"
>
  Load Data
</button>

<div if="spinner" style="display:none;" x-style="display:block;">
  Loading...
</div>

<div render="response"></div>
Enter fullscreen mode Exit fullscreen mode

If this idea feels interesting, you can explore the details here:


What just changed?

At first glance, not much.

But the mental model is completely different.

1. No element references

The button does not know:

  • where the response will render
  • where the loading indicator lives
  • whether either of them even exists

And the other elements don’t know about the button.

They just react.


One practical benefit of this approach is that these connections are no longer just strings.

Because actions are named identifiers, they can be statically analyzed and
tooled:

  • Go to definition / references
  • detection of undefined or unused actions
  • safe renaming across the codebase

This makes relationships between elements explicit and verifiable, rather than
relying on runtime selector matching.


2. Everything is explicit

There’s no hidden behavior:

  • on:click="send" → produces an action
  • on="send" → consumes it and sends a request
  • result="response" → produces another action
  • render="response" → consumes it
  • if:loading="spinner" → produces yet another action
  • if="spinner" → consumes it

No special cases. No magic elements. No CSS side channels.


3. “Loading” is not a feature

There is no such thing as a “loading indicator” in KEML.

There is only state.

State is declared using the pattern:

if:<condition>="name1 name2 ..."
Enter fullscreen mode Exit fullscreen mode

For example:

if:loading="spinner"
Enter fullscreen mode Exit fullscreen mode

This simply declares a state action called spinner that is activated during a loading request.

And once state exists, anything can react to it:

<div if="spinner" x-style="display:block;">
Enter fullscreen mode Exit fullscreen mode

This same pattern applies everywhere:

  • if:error="..." → error state
  • if:invalid="..." → validation state
  • if:value="..." → input state
  • if:intersects="..." → visibility state

But none of these are special cases.

They are all the same mechanism:

if:<condition> defines state, and if consumes it.


The if attribute has nothing to do with visibility (or loading) specifically.

It allows elements to declaratively configure any attribute value in the DOM
based on state
.

When a state is ON, attributes prefixed with x- temporarily override their
base value.

When the state is OFF, the original attribute values are restored.


The important difference

The easiest way to describe it is this:

KEML removes the need to reference other elements entirely.

In HTMX, you wire elements together:

  • “send this request”
  • “put the result there”
  • “show that spinner”

In KEML, you name things:

  • “this action happened”
  • “this result exists”
  • “this state is active”

And the DOM reacts.


Why this matters (even for small things)

For a tiny example like this, both approaches are fine.

But as things grow:

  • more triggers
  • more UI states
  • more places that react

Referencing elements starts to spread knowledge across your markup:

  • IDs
  • selectors
  • coupling between unrelated parts

KEML doesn’t prevent that.

It just… doesn’t require it.


One small gotcha

Actions are global.

So naming matters.

on:click="loadData"
Enter fullscreen mode Exit fullscreen mode

If multiple elements use loadData, they will all react.

This is intentional — but something to be aware of.


Final thought

KEML isn’t trying to replace HTMX.

They’re aiming at the same idea:

server-driven, HTML-first applications

KEML just takes a different route:

instead of connecting elements, it lets them react to signals.


If this made you pause for even a second — that’s enough.

Give it 5 minutes.

That’s all it really needs.

If you want to try it in a real project, it takes very little setup:

Top comments (0)