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>
.htmx-indicator {
display: none;
}
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
display: block;
}
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>
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 ..."
For example:
if:loading="spinner"
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;">
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, andifconsumes 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"
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)