A Better DOM API
HyperclayJS is the JavaScript library that powers single-file HTML apps. It gives your HTML files the ability to overwrite themselves, toggles between admin and viewer modes, and blurs the line between document and application.
Hyperclay is a unique system that lets you directly shape HTML files and have changes persist live on the web.
Features:
Build admin controls that are automatically stripped before saving and restored when you return, so regular users only ever see a clean static page.
One HTML file, full application.
Hyperclay pages update themselves. The save state is tracked via a savestatus attribute on the <html> element.
Persist page's HTML to a hyperclay server
hyperclay.savePage(callback);
CMD+S, [trigger-save] button, savestatus attribute
<html savestatus="saved">
<!-- saved | saving | dirty | error -->
Auto-save on DOM mutations
// Automatic - debounces saves
// after DOM changes
Toast on save events
// Shows toast on
// hyperclay:save-* events
Run JS after save events
<div onaftersave="this.textContent =
event.detail.status">
Cache-bust href/src attributes
<link href="styles.css"
onaftersave="cacheBust(this)">
Edit mode is a state defined by an editmode attribute on the <html> element. When true, admin controls are usable/visible; when false, they are disabled/hidden.
Switch edit mode on/off (affects other demos)
hyperclay.toggleEditMode();
hyperclay.isEditMode;
Only editable in edit mode, otherwise readonly
<div edit-mode-contenteditable>
Editable in edit mode
</div>
Inputs/selects editable only in edit mode
<input edit-mode-input>
<select edit-mode-input>
onclick handlers active only in edit mode
<button edit-mode-onclick
onclick="alert('Admin!')">
Scripts/styles active only in edit mode
<style edit-mode-resource>
.edit-outline { outline: 2px dashed blue; }
</style>
Persist form values to DOM attributes
<input persist>
<select persist>
Properties added to all elements that patch two gaps in the native DOM: traversing intuitively through the natural hierarchy, and getting/setting values in one short command.
Get/set attribute values on nearest matching element
<input a oninput="this.val.sum =
Number(this.val.a) + Number(this.val.b)">
Get/set innerText on nearest matching element
<button onclick="this.text.counter--">-</button>
<div counter>0</div>
Execute code stored in an attribute
<div confirm-delete="consent('Delete?',
() => this.remove())">
Nearest matching element (searches siblings, ancestors, and their descendants)
<span paintable>Item</span>
<button onclick="this.nearest.paintable
.style.background = randomColor()">
Show or hide elements based on the value of an attribute on an ancestor element. Uses the option: prefix.
Show elements based on hyperclay save state
<span option:savestatus="saved">
✓ Saved
</span>
Show elements only in hyperclay edit mode
<button option:editmode="true">
Edit Tools
</button>
Show elements based on any custom attribute
<div status="pending">
<span option:status="completed">
Custom event attributes that extend the native on* pattern to handle rendering, cloning, click-away, and drag-and-drop sorting.
Execute JS when element renders
<span onrender="this.textContent =
new Date().toLocaleTimeString()">
Execute JS when element is cloned
<div onclone="this.style.background =
randomColor()">
Trigger code when clicking outside element
<div onclick="reset"
onclickaway="trigger">
Drag-and-drop reordering with onsorted callback
<ul sortable onsorted="...">
<li>Item</li>
</ul>
The tersest way to work with multiple elements. All.x selects by attribute or class, native array methods transform them, and any DOM method applies to all at once.
Sync content across elements with same identifier
All.sync_card.forEach(c =>
c.val.project_name === this.val.project_name
&& c.innerHTML !== this.innerHTML
&& (c.innerHTML = this.innerHTML))
Filter elements with a dropdown
All.card.forEach(c =>
c.classList.toggle('muted', ...)
)
Run code when the DOM changes, scoped to a single element or the entire page.
Trigger code when this element or its children change
<div onmutation="console.log('changed')">
<span contenteditable>Edit</span>
</div>
Trigger code when any element on the page changes
<span onglobalmutation="this.textContent =
All('li').length">0</span>
Programmatic observer for added elements
Mutation.onAddElement({
selectorFilter: '.item'
}, changes => ...);
Standalone helpers for common tasks: DOM morphing, clipboard, slugs, query params, and style injection.
DOM morphing with content-based matching
HyperMorph.morph(el, newHtml);
Inject CSS (external or inline)
// External stylesheet
insertStyles('./test-style.css');
// Inline CSS with name
insertStyles('my-theme', '.foo { color: red }');
URL-friendly slug generator
slugify('Hello World!')
// "hello-world"
Copy text to clipboard
copyToClipboard('Hello!');
Parse URL search params
// ?name=dev
query.name // "dev"
The modals and prompts that should ship with the browser: ask for input, confirm an action, show a message.
Parent module for ask/tell/consent/snippet
themodal.html = '<h2>Custom</h2>';
themodal.open();
Edit attributes inline with ask() and el.nearest
ask('URL:', res => {
this.nearest.link.href = res;
})
Display informational message, returns promise
await tell('Welcome!', 'Info');
Get yes/no confirmation, returns promise
const ok = await consent('Delete?');
Show code snippet with copy button
await snippet('Install', code, 'Copy');
Show notification messages
toast('Saved!', 'success');
What native forms are missing: auto-growing textareas and serialization that handles nested objects and arrays.
Serialize form to object
const data = getDataFromForm(form);
Textarea grows with content
<textarea autosize></textarea>
Prevent Enter key, useful for inline editing with contenteditable elements
<span contenteditable prevent-enter>
Send form data and upload files to Hyperclay servers with one function call.
Submit form data to /message endpoint
await sendMessage(event, 'Sent!');
Upload file with progress toasts, copies URL to clipboard
<input type="file" onchange="uploadFile(event)">