esc
Anthology / Yagnipedia / Hyperscript

Hyperscript

Two Lines of Magic, Three Lines of Regret
Tool · First observed 2020 (Carson Gross, the same person who made HTMX, which explains the philosophy and excuses the syntax) · Severity: Delightful (in small doses), Hazardous (in large ones)

Hyperscript is a small, expressive scripting language for the web that reads like English, fits in an HTML attribute, and is absolutely wonderful until someone writes more than two lines of it.

Created by Carson Gross — the same person who created HTMX, which tells you everything about the philosophy and nothing about the syntax — Hyperscript is designed to handle the small, local, behavioral concerns that HTMX doesn’t: toggling a class, showing a modal, disabling a button after click, adding a transition. The things that live in the gap between “the server sends HTML” and “the HTML needs to do something small before the next server round-trip.”

Two lines of Hyperscript is magic. Three lines is where the trouble begins.

“Hyperscript is a spice. One pinch transforms the dish. Two pinches perfects it. The Squirrel emptied the jar. The dish is now a controlled substance.”
The Lizard, who seasons nothing and considers this optimal

The Love

Hyperscript lives in the _ attribute:

<button _="on click toggle .open on #menu">Menu</button>

Read it aloud. “On click, toggle the class ‘open’ on the element with ID ‘menu’.” It is English. Not idiomatic English — the kind of English that would get a C+ in composition — but English that a human can parse without a reference manual. Compare the JavaScript equivalent:

document.querySelector('#menu').classList.toggle('open')

The JavaScript is shorter. The JavaScript is also in a different language than the HTML it lives beside, which means the developer’s brain must context-switch between “I am reading a template” and “I am reading a program” at the boundary of every attribute. Hyperscript does not require this switch. Hyperscript is HTML-adjacent. Hyperscript reads like the markup, because Hyperscript was designed to be read like the markup.

For HTMX applications — where the server sends HTML and the client renders it — Hyperscript fills the gap perfectly. The server handles the data, the logic, the routing. HTMX handles the HTTP requests and DOM swaps. Hyperscript handles the micro-interactions: show this, hide that, toggle this class, wait 300 milliseconds, add a transition. The three together — HTMX for server communication, Hyperscript for local behavior, and custom CSS for styling — form a stack where the browser does what the browser does and nobody ships a virtual DOM.

The Templ Loophole

This is the part that makes a Go developer smile every time.

templ is Go’s HTML templating library — type-safe, compiled, strict. templ has opinions about inline JavaScript. Specifically: templ does not allow it. An onclick="doSomething()" attribute in a templ component produces a compile error. templ considers inline JavaScript a security risk, which it is, and a maintenance hazard, which it also is, and templ enforces this opinion at compile time, which is correct and responsible and exactly what a type-safe templating language should do.

templ does not have opinions about _.

The _ attribute is not JavaScript. The _ attribute is not recognized by templ as anything special. It is, as far as templ is concerned, a regular HTML attribute with a string value. templ compiles it. templ does not inspect it. templ looks the other way.

<button _="on click toggle .open on #menu">Menu</button>

templ compiles this without complaint. The inline behavior — the toggle, the class manipulation, the DOM interaction — sails through the type checker because it is not JavaScript. It is Hyperscript. It lives in _. templ does not know what _ is. templ does not care what _ is. templ has drawn a very clear line about inline onclick handlers and has apparently not noticed that the behavioral logic has moved one attribute to the left and changed languages.

This is not a bug. This is a feature. The _ attribute is the unmarked door in the type-safe corridor — the place where a small amount of client-side behavior can live without triggering the compiler’s security opinions. A developer uses this door frequently. The developer walks through it carrying one or two lines of Hyperscript. The developer does not carry more than two lines. The developer has learned what happens when you carry more than two lines.

The Roast

The third line of Hyperscript is where English becomes AppleScript and AppleScript becomes a cry for help.

Two lines:

<details _="on toggle if my open
              add .blur to #backdrop
            else
              remove .blur from #backdrop">

This is readable. This is maintainable. This is a developer expressing intent in a language that another developer can parse. Two lines. Four actions. Clear.

Now, a Squirrel-length Hyperscript:

<div _="on submit from closest <form/>
          halt the event
          set formData to the result of collectFormData()
          if formData.name is empty
            add .error to #name-field
            set #error-message's innerHTML to 'Name required'
            wait 3s
            remove .error from #name-field
          else
            put 'Submitting...' into #status
            fetch /api/submit with method:'POST' and body:formData as JSON
            if the result's status is 200
              put 'Success!' into #status
              add .fade-out to me
              wait 500ms
              remove me
            else
              put 'Failed. Retry?' into #status
              add .shake to #submit-btn
              wait 300ms
              remove .shake from #submit-btn">

This is twenty lines of Hyperscript. Read it aloud. Go ahead. Every line is English. Every line is comprehensible in isolation. Together, they form a paragraph-length program embedded in an HTML attribute, written in a language that has no debugger, no test framework, no linter, and no type system. The program validates a form, makes an HTTP request, handles success and failure states, manages CSS transitions, and performs timed cleanup — all in a string inside a _ attribute, where a typo on line 14 will fail silently at runtime and the developer will discover this when a customer reports that the submit button shakes but does not submit.

This is what happens when the Squirrel discovers Hyperscript.

“We could handle the ENTIRE form submission in Hyperscript! Validation! Error states! Loading indicators! Retry logic! It reads like ENGLISH!”
The Caffeinated Squirrel, already typing the twelfth line

“It reads like English the way AppleScript reads like English — technically true, practically incomprehensible, and impossible to debug.”
a developer, closing the PR

The AppleScript Parallel

The comparison to AppleScript is not casual. It is structural.

AppleScript was designed to read like English so that non-programmers could automate macOS. The result was a language where tell application "Finder" to get the name of every file in folder "Documents" is a valid program that reads like a sentence and debugs like a nightmare, because English is ambiguous and programming is not, and the gap between “reads like English” and “behaves like English” is where bugs live.

Hyperscript has the same gap. toggle .open on #menu is unambiguous. set the result of calling collectFormData() to formData and if formData.name is empty then add .error to the closest <input/> with id 'name' is technically English and practically a compressed program wearing a natural-language disguise.

The rule, which a developer enforces on himself and on every Claude session that proposes Hyperscript:

Two lines: Hyperscript.
More than two lines: JavaScript.

If the behavior is complex enough to need conditionals, HTTP requests, error handling, or state management — write it in JavaScript. In a .js file. With tests. With a debugger. With console.log. With all the tools that forty years of programming language development have produced for the specific purpose of writing, testing, and debugging programs.

Then call it from Hyperscript:

<button _="on click call submitForm(me)">Submit</button>

One line of Hyperscript. The logic lives in JavaScript. The JavaScript has tests. The tests run in CI. The behavior is verified before it ships. The Hyperscript is a bridge — two words that connect the HTML to the JavaScript, in a _ attribute that templ compiles without complaint and the developer reads without suffering.

This is the pattern. This is the only pattern. Anything more than this is trying to write SAP in AppleScript, which is a sentence that should never have needed to be written and which serves as its own argument.

The V4 Stack

The V4 product’s client-side architecture, in full:

HTMX         → server communication, DOM swaps
Hyperscript  → local micro-interactions (1-2 lines per component)
JavaScript   → complex behavior (tested, in .js files)
Custom CSS   → styling (domain-semantic, no framework)

Four layers. Each does one thing. Each stays in its lane. The Hyperscript layer is the thinnest — a pinch of spice per component, never the main course. The moment the Hyperscript grows beyond two lines, it is refactored into JavaScript and called from a one-line _ attribute. The refactoring is not optional. The refactoring is the line between “readable” and “AppleScript.”

“The developer has a rule: two lines of Hyperscript. The Squirrel has a rule: as many lines as the attribute will hold. The attribute will hold many lines. The attribute does not know it should refuse. The _ is too permissive. The _ needs a bouncer.”
The Passing AI, observing the attribute with concern

Measured Characteristics

See Also