The primary use case for hyperscript is adding interactivity to the DOM, so the language includes dedicated syntax for DOM work.
Earlier examples used much of this syntax in passing. This section walks through each piece in detail:
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.
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 provides special literals for the DOM.
Some are inspired by CSS, while others are our own creation.
Here is a table of the DOM literals:
| Literal | Syntax | Description |
|---|---|---|
| class literal | .class name .{expression} |
Starts with . and returns all elements with that class |
| ID literal | #ID #{expression} |
Starts with # and returns the element with that id |
| query literal | <css selector /> |
Contained within < and />, returns all elements matching the CSS selector |
| attribute literal | @attribute name |
Starts with @ (hence, attribute, get it?) and returns the value of that attribute |
| style literal | *style property |
Starts with * (a reference to CSS Tricks) and returns the value of that style property |
| measurement literal | 1em 0% expression px |
An expression followed by a CSS unit, appending the unit as a string |
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/>
Together, these constructs let you express DOM operations concisely. Compare the JavaScript equivalent:
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.
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
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/>
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
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.
Using the expressions above, you should be able to find the elements easily.
Content can be put into the DOM using the put command. To put content into an element (that is, into
its innerHTML) you can write:
<button _="on click put 'Clicked!' into me">
Click Me
</button>
You can also use modifiers to the put command to place content in different places relative to the target element:
<button _="on click put 'Clicked!' before me">
Click Me
</button>
The put command has the following modifiers:
| Syntax | Description |
|---|---|
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 |
If you want to replace content in the DOM, then the set command is the right
choice.
To replace an element entirely in the DOM you would say:
<button _="on click set #replace-me to 'Clicked!'">
Click Me
</button>
<span id="replace-me">Not Clicked Yet...</span>
Note that here the button is replaced entirely rather than the content being placed inside the button.
Content placed into the DOM with put or set is not HTML escaped.
This is intentional: it means you can insert rich content with tags, attributes, and structure into the page easity.
However, it also means that if the content comes from an untrusted source (a user, a URL parameter, a response body you don't control), you can introduce an XSS vulnerability.
For example, this is dangerous if userInput is not trusted:
put userInput into me -- HTML is parsed; <img onerror=...> will fire
set #greeting to userInput -- same risk, replacing the whole element
To insert text safely, target the textContent property instead of the element itself.
The browser will treat the value as plain text:
put userInput into my textContent -- safe, text-only
set my textContent to userInput -- same, different phrasing
A 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 do these operations.
Here are some examples adding, removing and toggling classes:
<button _="on click add .red to me">
Click Me
</button>
<button class="red" _="on click remove .red from me">
Click Me
</button>
<button _="on click toggle .red on me">
Click Me
</button>
You can also add, remove and toggle attributes as well. Here is an example:
<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:
<button _="on click toggle the *display of the next <p/>">
Toggle The Next Paragraph
</button>
<p>
Hyperscript is rad!
</p>
Hyperscript is rad!
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:
<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>
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
You can use the remove command to remove content from the DOM:
<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.
You can show and hide things with the show and hide commands:
<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:
<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:
<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>
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
You can transition a style from one state to another using the transition command. This
allows you to animate transitions between different states:
<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
element's style when the first transition begins.
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.
<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.
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.
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:
<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:
<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>
Hyperscript includes several additional commands for common DOM interactions:
The focus and blur commands set or remove keyboard focus:
focus #name-input
blur me
Both default to me if no target is given.
The empty command removes all children from an element:
empty #results
The select command selects the text content of an input or textarea:
select #search-input