Introduction

Hyperscript is a scripting language for doing front end web development.

Event Handling

A core feature of hyperscript is the ability to embed event handlers directly on HTML elements:

<button _="on click put 'I was clicked!' into me">
  Click Me!
</button>

Embedding event handling logic directly on elements in this manner makes it easier to understand what a given element does, and is in the same vein as many technologies that de-emphasize Separation of Concerns in favor of Locality of Behavior, such as tailwinds, AlpineJS and htmx.

Unlike the typical on* attributes, such as onClick, hyperscript's event handling syntax allows you to respond to any event, even custom events that you create or that are triggered by other libraries, and it gives you flexible control over how events are queued and filtered with a simple, clean syntax.

Hyperscript is expressive enough that many common UI patterns can be comfortably written directly inline in event handlers. If you need to, you can factor logic out to functions as well, and you can invoke javascript functions directly from hyperscript (and vice-versa). Frequently used or complex handlers can be streamlined with behaviors. Behaviors wrap up multiple features (event handlers, functions, unit blocks, etc.) with a shared state mechanism.

No More Promises

While powerful event handling is the most immediately practical feature of hyperscript, the most interesting technical aspect of the language is that it is async transparent.

That is, you can write code that is asynchronous, but in the standard linear style.

You may have noticed that the button above reset its text after a few seconds.

Here is what the hyperscript actually looks like on that button:

<button class='btn primary' _="on click put 'I was clicked!' into me
                                        wait 2s
                                        put 'Click Me' into me">
  Click Me!
</button>

Note the wait 2s command in this hyperscript. This tells hyperscript to, well, to wait for 2 seconds before continuing.

In javascript, this would be equivalent to the following onClick attribute:

var self = this;
self.innerHTML = "I was clicked!"
setTimeout(function(){
  self.innerHTML = "Click Me"
}, 2000)

In javascript you would either need to write the code in this asynchronous manner or, if you were getting fancy, use promises and then or the async/await keywords. You need to change your coding style to deal with the asynchronous nature of the timeout.

In contrast, using hyperscript you can simply write this code in the normal, linear fashion and the hyperscript runtime works everything out for you.

This means you can mix and match synchronous and asynchronous code freely, without needing to change coding styles. There is no distinction between red functions and blue functions in hyperscript.

This may seem a little silly for just making setTimeout() a little better looking, but async transparency allows you, for example, to move the message that is put into the button into an ajax call:

<button class='btn primary' _="on click fetch /clickedMessage
                                        put the result into me
                                        wait 2s
                                        put 'Click Me!' into me">
  Click Me!
</button>

Try it out, and check out the networking tab in your browsers development console:

Pretty neat, eh?

Let's jazz this example up a bit by adding some fade transitions

<button class='btn primary' _="on click fetch /clickedMessage then transition opacity to 0
                                        put the result into me then transition opacity to 1
                                        wait 2s then transition opacity to 0
                                        put 'Click Me!' into me then transition opacity to 1">
  Click Me!
</button>

Here we are using the then keyword to separate commands that are on the same line, grouping them logically and making the code read really nicely. We use the transition command to transition smoothly between opacities.

Note that the transition command is synchronized with the transition: it doesn't complete until the transition is done. The hyperscript runtime recognizes this and again allows you to write standard, linear-style code with it.

Now, this example is a little gratuitous, admittedly, but you can imagine what the equivalent javascript would look like: it would be a mess of confusing callbacks or would need to be transformed into a Promise-based style. The point isn't that you should use this particular UX pattern, but rather to show how async transparency can be used practically.

The Syntax

The syntax of hyperscript is very different syntax than most programming languages used today. It is based on an older and somewhat obscure (today) family of programming languages that originated in HyperTalk and that used a more natural language than the more familiar Algol-dervived languages we typically use today.

This unusual syntax has advantages, once you get over the initial hump:

  • It is distinctive, making it obvious when hyperscript is being used in a web page
  • It includes a native event handling syntax
  • It reads very easily, even if it can be hard to write at first
  • The commands in the language all have a strong start token, making parsing easier and the language easy to extend

Hyperscript favors read time over write time when it comes to code. It can be a bit tricky to get the syntax right at first (not so bad once you get used to it), but it reads very clearly once the code is written.

OK, let's get on with it...

Install & Quick Start

Hyperscript is a dependency-free javascript library that can be included in a web page without any build steps:

<script src="https://unpkg.com/hyperscript.org@0.8.1"></script>

After you've done this, you can begin adding hyperscript to elements:

<div _="on click call alert('You clicked me!')">
  Click Me!
</div>

Hyperscript has an open, pluggable grammar & some features do not ship by default (e.g. workers).

To use a feature like workers you can either:

  • install the extension directly by including /dist/workers.js after you include hyperscript
  • use the "Whole 9 Yards" version of hyperscript, which includes everything by default and can be found at /dist/hyperscript_w9y.js

The Language

A language is usually best learned, initially, by looking at examples.

Let's consider a simple event handler in hyperscript

<button _="on click add .aClass to #anotherDiv">
  Click Me!
</button>

Here we have a button. The button as an underscore attribute, which is the default attribute that hyperscript will look for, for scripts. The script contains an event handler the body of which will be executed when a click event is received.

The body of the event handler begins with an add command, which adds a class or attribute to a DOM element.

The add command takes, among other options, a CSS class literal. In this case the class literal is .aClass indicating that the aClass class should be added to something.

That something is specified with the to clause of the add command. In this case, we are adding the class to an ID literal, #anotherDiv which will look the element with the id anotherDiv up.

So, what does this all mean?

When the button is clicked hyperscript will add the .aClass to #anotherDiv.

Which is almost exactly what the handler says it does!

This is what hyperscript is designed for: reacting to events and updating DOM elements, acting as a glue language between various events and elements or components on the page.

The Basics

With that introduction, let's look at the broader language.

A hyperscript consists of one or more features.

The primary entry point into hyperscript is an event handler, which defines an event listener on a DOM element.

A feature then contains body which is a a series of commands (aka "statements"), a term taken from HyperTalk.

A command (usually) consists of a starting keyword and then a series of keywords, expressions and potentially child commands or command lists.

A command list is a series of commands, optionally separated by the then keyword:

<div _="on click add .fadeMe then wait 200ms then remove me">
  Fade & Remove Me
</div>

then acts roughly like a semi-colon in other languages. It is particularly recommended when multiple commands are on the same line, for clarity.

Expressions are the root syntactic element. Some should be very familiar to developers:

  • Number literals - 1.1
  • String literals = "hello world"
  • Array literals = [1, 2, 3]

Others are a bit more exotic:

  • ID Reference = #foo
  • Class Reference = .tabs
  • Query Reference = <div/>
  • Attribute Reference = @count

Below you will find an overview of the various features, commands and expressions in hyperscript, as well as links to more detailed treatments of them.

You may also want to simply head over to the cookbook for existing hyperscripts you can start using and modifying for your own needs.

Or, if you learn best by comparing code with things you already know, you can look at the VanillaJS/jQuery/hyperscript comparison.

Features

Top level constructs in hyperscript are called "features". They provide entry points into the hyperscript runtime through functions, event handlers and so forth. Features defined in a script tag will be applied to the document body and the global namespace.

Features defined on an element will be applied to that element and, in the cases of functions, etc. available to all child elements.

Below are the core features of hyperscript.

Event Handlers

Event handlers allow you to embed hyperscript logic directly in HTML to respond to events:

<button _="on click add .clicked">
  Add The "clicked" Class To Me
</button>

While the underscore (_) attribute is where the hyperscript runtime looks for hyperscript on an element by default, you may also use script or data-script attribute, or configure a different attribute name if you don't like any of those.

The script above says

On the 'click' event for this button, add the 'clicked' class to this button

Note that, unlike the previous example, here there is no to clause, so the add command defaults to "the current element".

The current element can be referred to with the symbol me (and also my, more on that in a bit).

Variables and Scope

hyperscript has 3 kinds of variables: local, element-scoped, and global. This section explains their differences with an example, and shows alternatives as well.

Local Scope

Consider the following snippet, which declares and increments the variable foo when the button is clicked:

<button _="on click increment foo then set my.innerText to foo">Bad Counter</button>

Clicking this button will set the button text to 1, no matter how many times you click. This is because by default, hyperscript uses local variables, which stop existing when the current event listener (or function, or whatever else) finishes. So, each time you click the button foo is initialized to zero, the button is set to 1, and then foo disappears.

Global Scope

To make a counter, we can use global variables:

<button _="on click increment global foo then set my.innerText to foo">Global Counter</button>

Now it works! Notice how we didn't need to write global the second time we used foo.

Those with programming experience will immediately see the issue. This is a fine pattern if the counter is the only thing on the page, but if we have two counters, they will increment the same foo. Even worse, if two different components of the page use the name foo for two different things, it'll likely be difficult to even figure out what's happening. See how clicking the counter above makes this identical counter below skip numbers:

Element Scope

To alleviate this issue, hyperscript also offers element-scoped variables:

<button _="on click increment element foo then set my.innerText to foo">Isolated Counter</button>

Here, the variable foo lasts as long as the button exists, and can only be accessed by the code of that element. This allows elements to have variables that stay around without interfering with one another.

If you set element-scoped variables from within a behavior, those will be visible from within that behavior only. This is to prevent name collisions between behaviors (esp. behaviors written by different people).

Attributes

Variables are not the only way to hold on to the data you're working on. In the version below, the variable foo is now declared as an HTML attribute of the button (which is also as persistent). Try clicking the counter with the browser inspector open.

Note that attributes can only store strings (anything else you put there will turn to a string, often in ways that you can't convert them back).

You can remember the @ sign as the ATtribute operator.

<button _="on click increment @foo then set my.innerText to @foo">Attribute Counter</button>

By combining these with an id selector, you can trivially manage state across elements. Consider this example, with two buttons allowing you to increment and decrement an attribute on another element.

<p id="counter">0</p>
<button _="on click increment #counter@foo then set #counter.innerText to #counter@foo">Add</button>
<button _="on click decrement #counter@foo then set #counter.innerText to #counter@foo">Subtract</button>

0

Technically, in the examples above, instead of using the foo attribute on the counter element, it might be easier to just use the counter.innerText instead, as shown below. Where and how you decide to store state will vary depending on what you are trying to accomplish.

<p id="counterText">0</p>
<button _="on click increment #counterText.innerText">Add</button>
<button _="on click decrement #counterText.innerText">Subtract</button>

0

Event Queueing

By default, the event handler will be run synchronously, so if the event is triggered again before the event handler finished, the new event will be queued and handled only when the current event handler finishes.

You can modify this behavior in a few different ways.

The Every Modifier

The with the every modifier will execute the event handler for every event that is received even if the preceding handler execution has not finished (due to some asynchronous operation.)

<button _="on every click add .clicked">
  Add The "clicked" Class To Me
</button>

This is useful in cases where you want to make sure you get the handler logic for every event going immediately.

The Queue Modifier

The every keyword is a prefix to the event name, but for other queuing options, you use postfix the event name with the queue keyword.

You may pick from one of four strategies:

  • none - Any events that arrive while the event handler is active will be dropped
  • all - All events that arrive will be added to a queue and handled in order
  • first - The first event that arrives will be queued, all others will be dropped
  • last - The last event that arrives will be queued, all others will be dropped

queued last is the default behavior

Event Destructuring

You may destructure event properties and details by appending a parenthesized list of names after the event name. This can be used to assign properties of either object to symbols that can be used in the body of the handler.

<button _="on click(button) log button">
  Log the event.button property
</button>

Event Filters

You can filter events by adding a bracketed expression after the event name and destructured properties (if any).

The expression should return a boolean value true if the event handler should execute.

Note that symbols referenced in the expression will be resolved as properties of the event, then as symbols in the global scope.

This lets you, for example, test for a middle click on the click event, by referencing the button property on that event directly:

<button _="on click[button==1] add .clicked">
  Middle-Click To Add The "clicked" Class To Me
</button>

Mutation Events

Hyperscript includes a few synthetic events that make use of more complex APIs. For example, you can listen for mutations on an element with the on mutation form. This will use the Mutation Observer API, but will act more like a regular event handler.

  <div _='on mutation of @foo put "Mutated" into me'></div>

This div will listen for mutations of the foo attribute on this div and, when one occurs, will put the value "Mutated" into the element.

Intersection Events

Another synthetic event is the intersection event that uses the Intersection Observer API. Again, hyperscript makes this API feel more event-driven:

<img _="on intersection(intersecting) having threshold 0.5
         if intersecting transition opacity to 1
         else transition opacity to 0 "
     src="https://placebear.com/200/300"/>

This image will become visible when 50% or more of it has scrolled into view. Note that the intersecting property is destructured into a local symbol, and the having threshold modifier is used to specify that 50% of the image must be showing.

Here is a demo:

Init Blocks

If you have logic that you wish to run at the point of loading, you may use an init block:

  <div _="init transition opacity to 1 over 3 seconds">
    Fade Me In
  </div>

The init keyword should be followed by a set of commands to execute when the element is loaded.

Functions

Functions in hyperscript are defined by using the def keyword.

Functions defined on elements will be available to the element the function is defined on, as well as any child elements.

Functions can also be defined in a hyperscript script tag:

<script type="text/hyperscript">
  def waitAndReturn()
    wait 2s
    return "I waited..."
  end
</script>

This will define a global function, waitAndReturn() that can be invoked from anywhere in hyperscript.

These scripts can also be loaded remotely, but in that case, they must appear before loading hyperscript:

<script type="text/hyperscript" src="/functions._hs"></script>
<script src="https://unpkg.com/hyperscript.org"></script>

Global hyperscript functions are available in javascript:

  var str = waitAndReturn();
  str.then(function(val){
    console.log("String is: " + val);
  })

In javascript, you must explicitly deal with the Promise created by the wait command. In hyperscript runtime, the runtime takes care of that for you behind the scenes.

Note that if you have a normal, synchronous function like this:

<script type="text/hyperscript">
  def waitAndReturn()
    return "I waited..."
  end
</script>

then javascript could use it the normal, synchronous way:

  var str = waitAndReturn();
  console.log("String is: " + str);

hyperscript functions can take parameters and return values in the expected way:

<script type="text/hyperscript">
  def increment(i)
    return i + 1
  end
</script>

Namespacing

You can namespace a function by prefixing it with dot separated identifiers. This allows you to place functions into a specific namespace, rather than polluting the global namespace:

<script type="text/hyperscript">
  def utils.increment(i)
    return i + 1
  end
</script>
<script>
  console.log(utils.increment(41)); // access it from javascript
</script>

Exceptions

A function may have one and only one catch block associated with it, in which to handle exceptions that occur during the execution of the body:

<script type="text/hyperscript">
  def example
    call mightThrowAnException()
  catch e
    log e
  end
</script>

Behaviors

Behaviors allow you to bundle together some hyperscript code (that would normally go in the _ attribute of an element) so that it can be "installed" on any other. They are defined with the behavior keyword:

behavior Removable
  on click
    remove me
  end
end

They can accept arguments:

behavior Removable(removeButton)
  on click from removeButton
    remove me
  end
end

They can be installed as shown:

<div class="banner" _="install Removable(removeButton: #close-banner)">
  ...

For a better example of a behavior, check out Draggable._hs.

Web Workers

WebWorkers can be defined inline in hyperscript by using the worker keyword.

The worker does not share a namespace with other code, it is in it's own isolated sandbox. However, you may interact with the worker via function calls, passing data back and forth in the normal manner.

<script type="text/hyperscript">
  worker Incrementer
    def increment(i)
      return i + 1
    end
  end
</script>
<button _="on click call Incrementer.increment(41) then put 'The answer is: ' + it into my.innerHTML">
  Call a Worker...
</button>

This makes it very easy to define and work with web workers.

Note that you can use the inline js feature discussed next if you want to use javascript in your worker. You might want to do this if you need better performance on calculations than hyperscript provides, for example.

Web Sockets

Web Sockets allow for two-way communication with a web server, and are becoming increasingly popular for building web applications. Hyperscript provides a simple way to create them, as well as a simple Remote Procedure Call (RPC) mechanism layered on top of them, by using the socket keyword.

Here is a simple web socket declaration in hyperscript:

socket MySocket ws://myserver.com/example
  on message as json
    log message
end

This socket will log all messages that it receives as a parsed JSON object.

You can send messages to the socket by using the normal send command:

    send myMessage(foo: "bar", doh: 42) to MySocket

You can read more about the RPC mechanism on the socket page.

Event Source

Server Sent Events are a simple way for your web server to push information directly to your clients that is supported by all modern browsers.

They provide real-time, uni-directional communication from your server to a browser. Server Sent Events cannot send information back to your server. If you need two-way communication, consider using sockets instead.

You can declare an SSE connection by using the eventsource keyword and can dynamically change the connected URL at any time without having to reconnect event listeners.

Here is an example event source in hyperscript:

eventsource ChatUpdates from http://myserver.com/chat-updates

    on message as string
        put it into #div
    end

    on open
        log "connection opened."
    end

end

This event source will put all message events in to the #div and will log when an open event occurs. This feature also publishes events, too, so you can listen for Server Sent Events from other parts of your code.

Inline JS

Inline javascript may be defined using the js keyword. You might do this for performance reasons, since the hyperscript runtime is more focused on flexibility, rather than performance.

This feature is useful in workers, when you want to pass javascript across to the worker's implementation:

<script type="text/hyperscript">
  worker CoinMiner
    js
      function mineNext() {
        // a javascript implementation...
      }
    end
    def nextCoin()
      return mineNext()
    end
  end
</script>

Note that there is also a way to include inline javascript directly within a hyperscript body, for local optimizations.

Commands

Commands are the statements of the hyperscript langauge, and make up the bodies of functions, event handlers and so on.

In hyperscript, (almost) all commands start with a term, such as add, remove or fetch.

Commands may be separated with a then keyword. This is recommended in one-liner event handlers but is not required.

Note that all commands may be followed by an unless <expr> that makes the command conditionally executed.

Default Commands

Below is a table of all commands available by default in the hyperscript language:

name description example
add Adds content to a given target add .myClass to me
append Appends a value to a string, array or HTML Element append "value" to myString
async Runs commands asynchronously async fetch /example
call/get Evaluates an expression (e.g. a Javascript Function) call alert('yep, you can call javascript!)

get prompt('Enter your name')
decrement Subtracts a value to a variable, property, or attribute decrement counter
fetch Send a fetch request fetch /demo then put it into my.innerHTML
go Navigate to a new page or within a page go to the top of the body smoothly
halt Halts the current event (stopping propogate, etc.) halt
hide Hide an element in the DOM hide me
if A conditional control flow command if me.selected then call alert('I\'m selected!')
increment Adds a value to a variable, property, or attribute increment counter
js Embeds javascript js alert('this is javascript'); end
log Logs a given expression to the console, if possible log me
make Creates a class instance or DOM element make a Set from a, b. c, make a <p/> called para
put Puts a value into a given variable or property put "cool!" into me.innerHTML
remove Removes content log "bye, bye" then remove me
repeat Iterates repeat for x in [1, 2, 3] log x end
return Returns a value return 42
send Sends an event send customEvent to #a-div
set Sets a variable or property to a given value set x to 0
settle Waits for a transition to end on an element, if any add .fade-out then settle
show Show an element in the DOM show #anotherDiv
take Takes a class from a set of elements take .active from .tabs
tell Temporarily sets a new implicit target value tell <p/> add .highlight
throw Throws an exception throw "Bad Value"
toggle Toggles content on a target toggle .clicked on me
transition Transitions properties on an element transition opacity to 0
trigger triggers an event on the current element trigger customEvent
wait Waits for an event or a given amount of time before resuming the command list wait 2s then remove me

Below, we will discuss some of the more commonly used commands.

Add

The add command allows you to add classes or properties to an element or multiple elements in the DOM.

Here are some examples:

<!-- adds the .clicked class to the div with id #aDiv -->
<button _="on click add .clicked to #aDiv">
  Click Me
</button>

You can add a class to multiple elements by using a class reference instead:

<!-- adds the .clicked class to all elements with the example class on it -->
<button _="on click add .clicked to .example">
  Click Me
</button>

If you omit a target, the default will be the current element.

<!-- adds the .clicked class to the button -->
<button _="on click add .clicked">
  Click Me
</button>

Remove

The remove command allows you to remove classes or properties to an element or multiple elements in the DOM.

Here are some examples:

<!-- removes the .blocked class from the div with id #aDiv -->
<button _="on click remove .blocked from #aDiv">
  Click Me
</button>

You can remove a class from multiple elements by using a class reference instead:

<!-- removes the .blocked class from all elements with the .example class on them -->
<button _="on click remove .blocked from .example">
  Click Me
</button>

If you omit a target, the default will be the current element.

<!-- removes the .blocked class from the button -->
<button _="on mouseover remove .blocked">
  Click Me
</button>

Toggle

The toggle command allows you to toggle a class or property on an element or multiple elements in the DOM. Depending on the syntax you pick, the toggle can be for a fixed amount of time, or until the reception of an event.

Here are some examples:

<!-- toggle the .checked class on the button -->
<button _="on click toggle .checked">
  Click Me
</button>

You can toggle for a fixed amount of time by using the for modifier:

<!-- toggle the .clicked class on the button for 2 seconds -->
<button _="on click toggle .clicked for 2 s">
  Click Me
</button>

You can toggle until an event is received by using the until modifier:

<!-- toggle the .clicked class on the button for 2 seconds -->
<button _="on click toggle .clicked until transitionend">
  Click Me
</button>

As with the add and remove commands, you can target other elements:

<!-- toggle the .checked class on the element with the id anotherDiv -->
<button _="on click toggle .checked on #anotherDiv">
  Click Me
</button>

Show

The show command allows you to show an element in the DOM using various strategies.

Here are some examples:

<!-- show the element after 2 seconds by setting display to block -->
<div _="on load wait 2s then show">
  I will show up in 2 seconds...
</div>
<!-- show the element after 2 seconds by setting opacity to 1 -->
<div _="on load wait 2s then show with opacity">
  I will show up in 2 seconds...
</div>
<!-- show the element after 2 seconds by setting display to inline-block -->
<div _="on load wait 2s then show with display:inline-block">
  I will show up in 2 seconds as inline-block...
</div>
<!-- show another element on click -->
<div _="on click show #anotherDiv">
Show Another Div
</div>

You can also plug in custom hide show strategies. See the command details for more information.

Hide

The hide command allows you to hide an element in the DOM using various strategies.

Here are some examples:

<!-- Hide the button -->
<button _="on click hide">
  Hide Me...
</button>
<!-- Hide the button with opacity set to 0-->
<button _="on click hide with opacity">
  Hide Me...
</button>
<!-- hide another element on click -->
<button _="on click hide #anotherDiv">
 Hide A Div
</button>

You can also plug in custom hide show strategies. See the command details for more information.

Wait

The wait command allows you to wait a fixed amount of time or to wait for an event. Execution will pause for the given period of time, but it will not block other events from happening.

Here are some examples:

<!-- wait 2 seconds and then remove the button -->
<button _="on click wait 2s then remove me">
  Click Me
</button>
<!-- add a fadeOut class, wait for the transition to end, then remove the button -->
<button _="on click add .fadeOut then wait for transitionend then remove me">
  Click Me
</button>

Transition

The transition command allows you to transition a style attribute from one value to another.

Here are some examples:

  transition my opacity to 0
  transition the div's opacity to 0
  transition #anotherDiv's opacity to 0 over 2 seconds
  transition .aClass's opacity to 0

Note that this command will not complete until the transition is done. This is in contrast with transitions kicked off by the add command adding a class to an element.

Settle

The settle command allows transitions that are kicked off by an add command to complete before progressing.

<button _="on click
                repeat 6 times
                  toggle .red then settle">
    You thought the blink tag was dead?
</button>

In this code, the .red class transitions the button to being red. The settle command detects that a transition has begun and then waits for it to complete, before another run through the loop.

Send

The send command sends an event to another element in the DOM. You can pass arguments to the other event via an optional named argument syntax.

Here are some an examples:

<div _="on click send doIt(answer:42) to #div1">Click Me!</div>
<div id="div1" _="on doIt(answer) put 'The answer is ' + answer into my.innerHTML"></div>
<div _="on click send remove to #div1">Click Me!</div>
<div id="div1" _="on remove remove me">I will be removed...</div>

Trigger

The trigger command triggers an event on the current element. You can use this to centralize logic in one event handler.

Here is an examples that centralizes logic to remove an element:

<div _="on click trigger remove
        on mouseout trigger remove
        on remove add .fadeOut then wait until transitionend then remove Me">Click Me!</div>

Take

The take command takes a class from another set of elements. This can be used to represent an active element in a tabbed UI, for example.

Here is an example on a parent element that takes the class .active and assigns it to the clicked anchor:

<div _="on click take .active from .tab for event.target">
    <a class="tab active">Tab 1</a>
    <a class="tab">Tab 2</a>
    <a class="tab">Tab 3</a>
</div>

Log

The log command logs a value to the console.

<div _="on click log me then remove me">
  Click Me
</div>

Call

The call command calls a function or evaluates an expression and puts the result into the result variable.

<div _="on click call getTheAnswer() then put it into my.innerHTML">
  Click Me
</div>

get is an alias for call:

<div _="on click get getTheAnswer() then put it into my.innerHTML">
  Click Me
</div>

You can choose the one that reads more clearly for your use case.

Put

The put command puts a value somewhere, either into the DOM, or into a variable or into a property.

Here is a basic example putting 'Clicked' into the divs innerHTML:

<div _="on click put 'Clicked!' into my.innerHTML">
  Click Me
</div>

Here is an example putting 'Clicked' after the button: (Additional clicks will keep adding "Clicked")

<div _="on click put 'Clicked!' after me">
  Click Me
</div>

Other positional options are:

  • before
  • at end of
  • at start of

Set

The set command sets a value somewhere, either into a variable or into a property.

Here is an example function setting a few variables

<script type="text/hyperscript">
  function numberString(total)
    set i to total
    set str to ""
    repeat while i > 0
      set str to str + i
      set i to i - 1
    end
    return str
  end
</script>

Set should be favored for setting local variables, where put should be favored for DOM manipulation.

If

The if command allows for conditional execution and works in the expected manner:

<script type="text/hyperscript">
  function isNegative(value)
    if i < 0
      return "The value is negative"
    else
      return "The value is non-negative"
    end
  end
</script>

Repeat

The repeat command is the looping construct in hyperscript and supports a large number of variants:

<script type="text/hyperscript">
  function loops()

    -- a basic for loop
    repeat for x in [1, 2, 3]
      log x
    end

    -- you may omit the 'repeat' keyword for a for loop
    for x in [1, 2, 3]
      log x
    end

    -- you may repeat without an explicit loop variable and use the implicit `it` symbol
    repeat in [1, 2, 3]
      log it
    end

    -- you may use a while clause to loop while a condition is true
    repeat while x < 10
      log x
    end

    -- you may use an until clause to loop until a condition is true
    repeat until x is 10
      log x
    end

    -- you may use the times clause to repeat a fixed number of times
    repeat 3 times
      log 'looping'
    end

    -- you may use the index clause on any of the above
    -- to bind the loop index to a given symbol
    for x in [1, 2, 3] index i
      log i, "is", x
    end

  end
</script>

Fetch

The fetch command issues an AJAX request using the fetch API. It is an asynchronous command but, as with all hyperscript, can be used in a linear manner without call backs.

The results of a fetch will be placed in the it variable. Note that by default the value will be text, but you can use the as json modifier to parse it as JSON.

Here are some example usages:


<button _="on click fetch /example then put it into my.innerHTML">
  Fetch it!
</button>

<button _="on click fetch /example as json then put it.message into my.innerHTML">
  Fetch it as JSON!
</button>

<button _="on click fetch /example {method:'POST'} then put it into my.innerHTML">
  Fetch it with a POST!
</button>

Inline Javascript

Performance is an, ahem, secondary consideration in hyperscript and, while it is fast enough in most cases, there are times when you may want to kick out to javascript. You may of course pull logic out to a javascript function and invoke it from hyperscript, but you can also inline javascript using the js command.

Here are some examples:

<button _="on click js
                      console.log('this is js code..');
                    end">
  Click Me
</button>

You can pass variables into the block like so:

<button _="on click js(me)
                      console.log(me);
                    end">
  Click Me
</button>

And you can use any results return from the javascript in the default it variable:

<button _="on click js
                      return 'This is from javascript...";
                    end then log it">
  Click Me
</button>

Pseudo-Commands

While almost all commands have a specific start symbol, there is one other type of command: pseudo-commands.

Pseudo-commands allow you to treat a method on an object as a command. The method name must be followed by an argument list, then optional prepositional syntax to clarify the code, then an expression. The method will be looked up on the value returned by the expression and executed.

Consider the refresh() method found on window.location. In hyperscript, you can use it as a pseudo-command like so:

  <button _="on click refresh() the location of the window">
    Refresh the Location
  </button>

Expressions

Expressions in hyperscript are a mix of familiar ideas from javascript and friends, and then some more exotic expressions.

You can see the expressions page for more detail.

Things Similar to Javascript

Strings, numbers, true, false and null all work as you'd expect.

Mathematical operators work normally except that you cannot mix different mathematical operators without parenthesizing them to clarify binding.

  set x to 1 * (2 + 3)

Logical operators use the english terms and, or and not and must also be fully disambiguated.

  if foo and bar
    log "Foo & Bar!"
  end

Comparison operators are the normal '==', '<=', etc. You can is is and is not in place of == and !== respectively.

  if foo is bar
    log "Foo is Bar!"
  end

Hyperscript also supports a no operator to test for == null

  if no foo
    log "foo was null-ish"
  end

Array and object literals work the same as in javascript:

  set x to {arr:[1, 2, 3]}

Things Different from JavaScript

While some expressions work the same as JavaScript, many things are different. Here are some of the bigger differences:

Closures

Closures in hyperscript have a different syntax, starting with a backslash and followed by an arrow:

    set arr to ["a", "ab", "abc"]
    set lengths to arr.map( \ str -> str.length )

Hyperscript closures may only have an expression body. They cannot have return statements, etc. in them. Because hyperscript is async-transparent, the need for complex callbacks is minimized, and by only allowing expressions in closure bodies, hyperscript eliminates ambiguity around things like return, throw etc.

CSS Literals

Hyperscript supports many literal values for DOM access:

  • Class Literals - .myClass
  • ID Literals - #someElement
  • Query Literals - <p.hidden/>
  • Attribute Literals - @href

Each of these can be used to refer to various elements in or attributes on those elements.

  add .disabled to #myDiv
  for tab in <div.tabs/>
    add .highlight to tab
    set @highligted of the tab to true
  end

In the above code, .disabled refers to the class disabled and #myDiv evaluates to the element with the ID myDiv.

In the loop we iterate over all div with the tab class using a CSS selector literal.

We add the highlight class to them.

And then we set the attribute highlighted to true using an attribute literal: @highlighted

Possessive's, Of Expressions and "The"

One of the more humorous aspects of hyperscript is the inclusion of alternative possessive expressions, which allows you to replace the normal dot notation of property chains with a few different syntaxes.

The first form uses the english style 's or my symbol:

  set my innerText to 'foo'
  set #anotherDiv's innerText to 'bar'

These are the equivalent to the more conventional following dot notation:

  set me.innerText to 'foo'
  set #anotherDiv.innerText to 'bar'

Alternatively you can use the of expression:

  set innerText of me to 'foo'
  set innerText of #anotherDiv to 'bar'

Finally, as mentioned above, you can add a definite article the to the code to clarify the language:

  set the innerText of me to 'foo'
  set the innerText of #anotherDiv to 'bar'

the can appear as a start symbol for any expression, and should be used judiciously to clarify code for future readers.

  <button _="on click refresh() the location of the window">
    Refresh the Location
  </button>

Here we use a pseudo-command to refresh the location of the window.

The Hyperscript Zoo

Hyperscript supports a number of symbols that have specific meanings

name description
result the result of the last command, if any (e.g. a call or fetch)
it alias for result
its alias for result
me the element that the current event handler is running on
my alias for me
I alias for me
event the event that triggered the event current handler, if any
body the body of the current document, if any
detail the detail of the event that triggered the current handler, if any

Async Transparency

A feature that sets hyperscript apart from other languages is that it is async transparent: the runtime largely hides the distinction between asynchronous code and synchronous code from the script writer.

You can write a hyperscript function that looks like this:

<script type="text/hyperscript">
def theAnswer()
  return 42
end
</script>

And then invoke it from an event handler like so:

<button _="on click put theAnswer() into my innerHTML">
  Get The Answer...
</button>

So far, so synchronous.

However, if you updated the function to include a wait command:

<script type="text/hyperscript">
def theAnswer()
  wait 2s
  return 42
end
</script>

Suddenly the function becomes asynchronous.

Under the covers, that wait command turns into a setTimeout() and, if you invoke the method from javascript (which is perfectly acceptable) you would see that the result was a Promise rather than a number!

And now, here's the trick: the event handler that we defined earlier:

<button _="on click put theAnswer() into my innerHTML">
  Get The Answer...
</button>

This still works exactly the same, without modification.

You don't need to deal with the promise that was returned by theAnswer(). Instead, the hyperscript runtime takes care of it for you and, when the Promise from theAnswer() resolves, hyperscript will continue executing.

No async/await, no callbacks, no .then() invocations.

It just keeps working.

Now, that might seem like a parlor trick, but what's the real world value?

Well, what if we wanted to fetch the value from the server?

That involves an asynchronous call to the fetch() API, and the hyperscript runtime is fine with that as well:

<script type="text/hyperscript">
def theAnswer()
  fetch /theAnswer
  return it
end
</script>

Again, the original event handler does not need to be updated to deal with asynchronous code. When the value returns from the server, the hyperscript runtime will take care of resuming execution of the event handler.

Now how much would you pay? :)

The async keyword

That's all well and good (and it is well and good) but what if you want an operation to be asynchronous? Sometimes that comes up.

Hyperscript provides an async keyword that will tell the runtime not to synchronize on a value. So, if you wanted to invoke theAnswer() but not wait on it to return, you could update the event handler to look like this:

<button _="on click call async theAnswer() put 'I called it...' into my.innerHTML">
  Get The Answer...
</button>

Event Driven Control Flow

One of the extremely interesting abilities that this async-transparent runtime gives hyperscript is the ability to have event driven control flow:

<button id="pulsar"
        _="on click repeat until event stop
                      add .pulse then settle
                      remove .pulse then settle
                    end">
  Click me to Pulse...
</button>

<button _="on click send stop to #pulsar">
  Stop it from Pulsing
</button>

Here we have a button that, when clicked, will cycle between having the .pulse class on it and off it, with some sort of transition defined for that class in CSS. It will keep cycling through this loop until the button receives a stop event, which another other button will helpfully send to it.

Here is a demo of this code:

Click me to Pulse...
Stop it from Pulsing

What's particularly interesting here is that the CSS transition is allowed to finish smoothly, rather than abruptly, because the event listener that terminates the loop is only consulted once a complete loop is made.

This I hope gives you a taste of the unique execution model of hyperscript, and what uses it might be put to.

Debugging (Alpha)

Note: The hyperscript debugger is in alpha and, like the rest of the language, is undergoing active development

Hyperscript includes a debugger, hdb, that allows you to debug by inserting breakpoint commands in your hyperscript.

To use it you need to include the lib/hdb.js file. You can then add breakpoint commands in your hyperscript to trigger the debugger.

<div>
Debug: <input id="debug-on" type='checkbox' checked="checked">
</div>
<button _="on click
             if #debug-on matches <:checked/>
               breakpoint
             end
             tell #debug-demo
               transition 'background-color' to red
               transition 'background-color' to green
               transition 'background-color' to blue
               transition 'background-color' to initial
           ">Colorize...</button>

<div id="debug-demo">TechnoColor Dream Debugging...</div>
Debug:
TechniColor Dream Debugging...

Extending

Hyperscript has a pluggable grammar that allows you to define new features, commands and certain types of expressions.

Here is an example that adds a new command, foo, that logs `"A Wild Foo Was Found!" if the value of its expression was "foo":

    _hyperscript.addCommand('foo', function(parser, runtime, tokens) { // register for the command keyword "foo"

        if(tokens.match('foo')){                                       // consume the keyword "foo"

            var expr = parser.requireElement('expression', tokens);    // parse an expression for the value

            var fooCmd = {
                expr: expr,    // store the expression on the element

                args: [expr],  // return all necessary expressions for
                               // the command to execute for evaluation by
                               // the runtime

                op: function (context, value) {                 // implement the logic of the command
                    if(value == "foo"){                         // taking the runtime context and the value
                        console.log("A Wild Foo Was Found!")    // of the result of evaluating the expr expression
                    }

                    return runtime.findNext(this)               // return the next command to execute
                                                                // (you may also return a promise)
                }
            }
            return fooCmd; // return the new command object
        }
    })

With this command defined you can now write the following hyperscript:

  def testFoo()
    set str to "foo"
    foo str
  end

And "A Wild Foo Was Found!" would be printed to the console.

Security

Hyperscript allows you to define logic directly in your DOM. This has a number of advantages, the largest being Locality of Behavior making your system more coherent.

One concern with this approach, however, is security. This is especially the case if you are injecting user-created content into your site without any sort of HTML escaping discipline.

You should, of course, escape all 3rd party untrusted content that is injected into your site to prevent, among other issues, XSS attacks. The _, script and data-script attributes, as well as inline <script> tags should all be filtered.

Note that it is important to understand that hyperscript is interpreted and, thus, does not use eval (except for the inline js features). You (or your security team) may use a CSP that disallows inline scripting. This will have no effect on hyperscript functionality, and is almost certainly not what you (or your security team) intends.

To address this, if you don't want a particular part of the DOM to allow for hyperscript interpretation, you may place a disable-scripting or data-disable-scripting attribute on the enclosing element of that area.

This will prevent hyperscript from executing within that area in the DOM:

  <div data-disable-scripting>
    <%= user_content %>
  </div>

This approach allows you enjoy the benefits of Locality of Behavior while still providing additional safety if your HTML-escaping discipline fails.

History, or 'Yet Another Language?'

I know, I know.

Why on earth do we need yet another front end technology, let alone another scripting language?

Well, I'll tell you why:

The initial motivation for the language was the event model in htmx. I wanted to have a way to utilize these events naturally and directly within HTML. HTML tags support on* attributes for handling standard DOM events (e.g. onClick) but that doesn't work for custom events like htmx:load.

In intercooler, I had handled this by adding a bunch of custom event attributes, but that always felt hacky and wasn't general enough to handle custom events triggered by response headers, etc.

Additionally, I wanted to have a way to address some useful features from intercooler.js, but without causing htmx to bloat up and lose focus on the core request/response processing infrastructure:

Finally, after having spent many, many years chasing down event handlers defined in jQuery, often in obscure and, at times, insane places, I wanted to return to the simplicity of something like HyperCard, where the logic was right there, associated with the elements.

The more I looked at it, the more I thought that there was a need for a small, domain specific language for all this, rather than an explosion in attributes and inline javascript, or a hacky custom syntax as with ic-action. ic-action had the prototype for async tranparency idea in it, and I thought it would be interesting to see if we could solve the aync/sync problem with this language as well.

And so here we are. Yes, another programming language.