Working With The DOM

The primary use case for hyperscript is adding small bits of interactivity to the DOM and, as such, it has a lot of syntax for making this easy and natural.

We have glossed over a lot of this syntax in previous examples (we hope it was intuitive enough!) but now we will get into the details of what they all do:

Finding Elements

There are two sides to DOM manipulation: finding stuff and mutating it. In this section we will focus on how to find things in the DOM.

DOM Literals

You are probably used to things like number literals (e.g. 1) or string literals (e.g. "hello world").

Since hyperscript is designed for DOM manipulation, it supports special literals that make it easy to work with the DOM.

Some are inspired by CSS, while others are our own creation.

Here is a table of the DOM literals:

.class name
.{expression}

A class literal starts with a . and returns all elements with that class.

#ID
#{expression}

An ID literal starts with a # and returns the element with that id.

<css selector />

A query literal is contained within a < and />, returns all elements matching the CSS selector.

@attribute name

An attribute literal starts with an @ (hence, attribute, get it?) and returns the value of that attribute.

*style property

A style literal starts with an * (a reference to CSS Tricks) and returns the value of that style property.

1em
0%
expression px

A measurement literal is an expression followed by a CSS unit, and it appends the unit as a string. So, the above expressions are the same as "1em", "0%" and `${expression}px`.

Here are a few examples of these literals in action:

-- adds the 'disabled' class to the element with the id 'myDiv'
add .disabled to #myDiv

-- adds the 'highlight' class to all divs with the class 'tabs' on them
add .highlight to <div.tabs/>

-- sets the width of the current element to 35 pixels
set my *width to 35px

-- adds the `disabled` attribute to the current element
add @disabled to me

Class literals, ID Literals and Query Literals all support a templating syntax.

This allows you to look up elements based on a variable rather than a fixed value:

-- adds the 'disabled' class to the element with the id 'myDiv'
set idToDisable to 'myDiv'
add .disabled to #{idToDisable}

-- adds the 'highlight' class to all elements with the 'tabs' class
set classToHighlight to 'tabs'
add .highlight to .{classToHighlight}

-- removes all divs w/ class .hidden on them from the DOM
set elementType to 'div'
remove <${elementType}.hidden/>

All these language constructs make it very easy to work with the DOM in a concise, enjoyable manner.

Compare the following JavaScript:

document.querySelector('#example-btn')
  .addEventListener('click', e => {
    document.querySelectorAll(".elements-to-remove").forEach(value => value.remove());
})

with the corresponding hyperscript:

on click from #example-btn
  remove .elements-to-remove

You can see how the support for CSS literals directly in hyperscript makes for a much cleaner script, allowing us to focus on the logic at hand.

Finding Things In Other Things

Often you want to find things within a particular element. To do this you can use the in expression:

-- add the class 'highlight' to all paragraph tags in the current element
add .highlight to <p/> in me

Finding The Closest Matching (Parent) Element

Sometimes you wish to find the closest element in a parent hierarchy that matches some selector. In JavaScript you might use the closest() function

To do this in hyperscript you can use the closest expression:

-- add the class 'highlight' to the closest table row to the current element
add .highlight to the closest <tr/>

Note that closest starts with the current element and recurses up the DOM from there. If you wish to start at the parent instead, you can use this form:

-- add the class 'highlight' to the closest div to the current element, excluding the current element
add .highlight to the closest parent <div/>

Finding Things By Position

You can use the positional expressions to get the first, last or a random element from a collection of things:

-- add the class 'highlight' to the first paragraph tag in the current element
add .highlight to the first <p/> in me

Finding Things Relative To Other Things

You can use the relative positional expressions next and previous to get an element relative to either the current element, or to another element:

-- add the class 'highlight' to the next paragraph found in a forward scan of the DOM
add .highlight to the next <p/>

Note that next and previous support wrapping, if you want that.

Updating The DOM

Using the expressions above, you should be able to find the elements you want to update easily.

Now, on to updating them!

Set & Put

The most basic way to update contents in the DOM is using the set and put commands. Recall that these commands can also be used to set local variables.

When it comes to updating DOM elements, the put command is much more flexible, as we will see.

First, let's just set the innerHTML of an element to a string:

Example: Setting innerHTML
<button _="on click set my innerHTML to 'Clicked!'">
  Click Me
</button>

Using the put command would look like this:

Example: Setting properties with "put"
<button _="on click put 'Clicked!' into my innerHTML">
  Click Me
</button>

In fact, the put command is smart enough to default to innerHTML when you put something into an element, so we can omit the innerHTML entirely:

Example: Putting things into elements
<button _="on click put 'Clicked!' into me">
  Click Me
</button>

The put command can also place content in different places based on how it is used:

Example: Put X before Y
<button _="on click put 'Clicked!' before me">
  Click Me
</button>

The put command can be used in the following ways:

put content before element

Puts the content in front of the element, using Element.before.

put content at the start of element

Puts the content at the beginning of the element, using Element.prepend.

put content at the end of element

Puts the content at the end of the element, using Element.append.

put content after element

Puts the content after the element, using Element.after.

This flexibility is why we generally recommend the put command when updating content in the DOM.

Setting Attributes

One exception to this rule is when setting attributes, which we typically recommend using set.

It just reads better to us:

Example: Setting attributes
<button _="on click set @disabled to 'disabled'">
  Disable Me
</button>

set is recommended for setting values into normal variables as well.

The default command sets a variable only if it is currently null, undefined, or empty:

default x to 10       -- only sets x if it has no value
default @count to 0   -- works with attributes too

Add, Remove & Toggle

A very common operation in front end scripting is adding or removing classes or attributes from DOM elements. hyperscript supports the add, remove and toggle commands to help do this.

Here are some examples adding, removing and toggling classes:

Example: "add" command
<button _="on click add .red to me">
  Click Me
</button>
Example: "remove" command
<button class="red" _="on click remove .red from me">
  Click Me
</button>
Example: "toggle" command
<button _="on click toggle .red on me">
  Click Me
</button>

You can also add, remove and toggle attributes as well. Here is an example:

Example: Toggle an attribute
<button _="on click toggle @disabled on #say-hello">
  Toggle Disabled State
</button>
<button id="say-hello" _="on click alert('hello!')">
  Say Hello
</button>

Finally, you can toggle the visibility of elements by toggling a style literal:

Example: Toggle an attribute
<button _="on click toggle the *display of the next <p/>">
  Toggle The Next Paragraph
</button>
<p>
  Hyperscript is rad!
</p>

Hyperscript is rad!

Taking Classes & Attributes

The take command removes a class (or attribute) from a set of elements and adds it to a target, making it perfect for "active item" patterns like tab bars and menus:

Example: Take a class
<ul _="on click from <li/>
         take .selected from <li/> for the target">
  <li>Tab 1</li>
  <li>Tab 2</li>
  <li>Tab 3</li>
</ul>
  • Tab 1
  • Tab 2
  • Tab 3

This removes .selected from all <li> elements and adds it to the one that was clicked.

You can also take attributes with an optional replacement value:

take @aria-selected with "true" from <li/> for the target
Removing Content

You can also use the remove command to remove content from the DOM:

Example: Remove an element
<button _="on click remove me">
  Remove Me
</button>

The remove command is smart enough to figure out what you want to happen based on what you tell it to remove.

Showing & Hiding Things

You can show and hide things with the show and hide commands:

Example: Show, Hide
<button _="on click
               hide me
               wait 2s
               show me">
               Peekaboo
</button>

By default, the show and hide commands will use the display style property. You can instead use visibility or opacity with the following syntax:

Example: Show/hide strategies
<button _="on click
               hide me with *opacity
               wait 2s
               show me with *opacity">
               Peekaboo
</button>

The add, remove, show and hide commands all support a when clause to conditionally apply to each element. After execution, the result contains the array of elements that matched the condition.

Here is an example using show ... when to filter a list:

Example: Filter elements with `show ... when`
<input _="on keyup show <li/> in #color-list
                     when its innerHTML contains my value">
<ul id="color-list">
  <li>Red</li>
  <li>Blue</li>
  <li>Blueish Green</li>
  <li>Green</li>
  <li>Yellow</li>
</ul>

  • Red
  • Blue
  • Blueish Green
  • Green
  • Yellow

We mentioned this above, but as a reminder, you can toggle visibility using the toggle command:

Example: Toggle visibility
<button _="on click toggle the *display of the next <p/>">
  Toggle The Next Paragraph
</button>
<p>
  Hyperscript is rad!
</p>

Hyperscript is rad!

Transitions

You can transition a style from one state to another using the transition command. This allows you to animate transitions between different states:

Example: "transition" command
<button _="on click transition my *font-size to 30px
               then wait 2s
               then transition my *font-size to initial">
  Transition My Font Size
</button>

The above example makes use of the special initial symbol, which you can use to refer to the initial value of an elements style when the first transition begins.

Class-Based Transitions

The transition command is blocking: it will wait until the transition completes before the next command executes.

Another common way to trigger transitions is by adding or removing classes or setting styles directly on an element.

However, commands like add, set, etc. do not block on transitions.

If you wish to wait until a transition completes after adding a new class, you should use the settle command which will let any transitions that are triggered by adding or removing a class finish before continuing.

Example: Wait for transitions/animations to finish
<button style="transition: all 800ms ease-in"
         _="on click add .red then settle then remove .red">
  Flash Red
</button>

If the above code did not have the settle command, the button would not flash red because the class .red would be added and then removed immediately

This would not allow the 800ms transition to .red to complete.

View Transitions

The start a view transition command wraps DOM mutations in a View Transition, animating between before and after snapshots of the DOM:

start a view transition
    put newContent into #container
end

You can specify a transition type for CSS targeting:

start a view transition using "slide-left"
    remove .active from .tab
    add .active to me
    put content into #panel
end

All animation timing and style is controlled via CSS (e.g. ::view-transition-old, ::view-transition-new). If the browser does not support view transitions, the body runs normally with no animation.

See the start a view transition command for full details.

Measuring Things

Sometimes you want to know the dimensions of an element in the DOM in order to perform some sort of translation or transition. Hyperscript has a measure command that will give you measurement information for an element:

Example: Measure an Element
<button _="on click measure my top then
                    put `My top is ${top}` into the next <output/>">
Click Me To Measure My Top
</button>
<output>--</output>

--

You can also use the pseudo-style literal form *computed-<style property> to get the computed (actual) style property value for an element:

Example: Get A Styles Computed Value
<button _="on click get my *computed-width
                    put `My width is ${the result}` into the next <output/>">
Click Me To Get My Computed Width
</button>
<output>--</output>

--

Other DOM Operations

Hyperscript includes several additional commands for common DOM interactions:

Focus & Blur

The focus and blur commands set or remove keyboard focus:

focus #name-input
blur me

Both default to me if no target is given.

Empty

The empty command removes all children from an element:

empty #results

Select

The select command selects the text content of an input or textarea:

select #search-input

Open & Close

The open and close commands work with dialogs, details elements and popovers:

open #my-dialog      -- calls showModal() on a <dialog>
close #my-dialog     -- calls close() on a <dialog>
open #my-details     -- sets open attribute on a <details>
close #my-details    -- removes open attribute from a <details>

For elements with a popover attribute, open and close call showPopover() and hidePopover() respectively. As a fallback, they call .open() and .close() on the target.

You can also enter and exit fullscreen mode:

open fullscreen #video
close fullscreen

Ask & Answer

The ask and answer commands provide access to the browser's built-in dialogs:

ask "What is your name?"
put it into #greeting

answer "File saved!"

ask wraps prompt() and places the result in it. answer wraps alert() by default.

With two choices, answer wraps confirm() and the result is the chosen label:

answer "Save changes?" with "Yes" or "No"
if it is "Yes"
  -- save...
end

Speech

As a nod to HyperTalk, hyperscript includes a speak command that uses the Web Speech API for text-to-speech:

speak "Hello world"
speak "Hello" with voice "Samantha"
speak "Quickly now" with rate 2 with pitch 1.5

The command is async-transparent: it waits for the utterance to finish before continuing to the next command. You can configure voice, rate, pitch, and volume using with clauses.