The LLM-First JavaScript Framework
boreDOM is a specialized runtime designed to allow Large Language Models (LLMs) to generate, maintain, and deliver complex web applications contained entirely within a single HTML file.
It eliminates the "context window thrashing" caused by modern build tools, bundlers, and multi-file dependencies, allowing LLMs to focus purely on logic and structure.
Modern frameworks (React, Vue, Svelte) are optimized for human teams working in long-term repositories. They rely on:
- Complex build steps (
npm install,vite) - File splitting (Components, CSS, Logic scattered)
- Implicit knowledge (Config files, distinct syntaxes)
For an LLM, this is Cognitive Overload. To change a button color, it needs context from three different files. To add a feature, it must hallucinate a build pipeline.
boreDOM flips the script:
- Single File Delivery: Logic, UI, Styles, and Runtime live in one
.htmlfile. Zero context switching. - Explicit Context: Everything needed to run the component is defined on the component.
- Zero-Build: It runs directly in the browser. What you generate is what you see.
- Zero-Build Componentization: Components are defined via standard HTML
<template>,<style>, and<script>tags. boreDOM upgrades them into light DOM Custom Elements at runtime. - Declarative Bindings: UI updates are handled via
data-attributes (data-text,data-list), reducing the need for fragile DOM manipulation code. - Composable Styling: Component styles remain separate and can be layered via CSS Layers for reuse.
- The default and supported path is single-file, zero-build apps.
- The repository includes an experimental multi-file authoring workflow under
packages/that compiles back to single-file output. - Use experimental mode only when you explicitly need it.
An entire interactive Counter app in one file:
<!DOCTYPE html>
<html>
<body>
<!-- 1. INITIAL STATE -->
<script id="initial-state" type="application/json">
{ "count": 0 }
</script>
<!-- 2. USE COMPONENT -->
<simple-counter></simple-counter>
<!-- 3. DEFINE COMPONENT -->
<!-- Scoped Styles -->
<style data-component="simple-counter">
@layer components.simple-counter {
simple-counter .counter { display: flex; gap: 10px; font-family: sans-serif; }
simple-counter button { background: #007bff; color: white; border: none; padding: 5px 10px; }
}
</style>
<!-- HTML Template -->
<template data-component="simple-counter">
<div class="counter">
<button data-dispatch="decrement">-</button>
<span data-text="local.count" style="font-weight: bold;"></span>
<button data-dispatch="increment">+</button>
</div>
</template>
<!-- Logic (ES Module) -->
<script type="text/boredom" data-component="simple-counter">
export default ({ on, local }) => {
// 1. Initialize Local State
local.count = 0;
on("increment", ({ local }) => { local.count++ });
on("decrement", ({ local }) => { local.count-- });
};
</script>
<!-- 4. INLINE RUNTIME (here used as src="" for example purposes)-->
<script src="./boreDOM.js" data-state="#initial-state"></script>
</body>
</html>boreDOM acts as a "JIT Compiler" for the browser:
- Scan: It finds all
<template>,<style>, and<script>tags withdata-component="name". - Register: It defines a custom Web Component (e.g.,
<simple-counter>) in light DOM. - Hydrate: When the component mounts:
- Styles are injected into
<head>(use CSS Layers to scope/reuse). - The Template is cloned into the component element.
- The Script is loaded as a Blob Module and executed.
- Event listeners (
data-dispatch) are wired up automatically.
- Styles are injected into
| Directive | Usage | Description |
|---|---|---|
data-text |
data-text="state.count" |
Sets textContent to the result of the expression. |
data-show |
data-show="state.isVisible" |
Toggles display: none based on truthiness. |
data-value |
data-value="state.inputValue" |
Two-way binding for assignable paths (falls back to one-way for non-assignable expressions). |
data-checked |
data-checked="state.isOn" |
Sets checkbox/radio checked state. |
data-class |
data-class="active:state.isOn; muted:!state.isOn" |
Toggles one or more classes based on conditions. |
data-list |
data-list="state.items" or data-list="item in state.items" |
Renders a list. Must contain a <template data-item> |
data-list-key |
data-list-key="item.id" |
Enables keyed updates to keep list nodes stable. |
data-list-once |
data-list-once |
Renders a list only once (static lists, alias: data-list-static). |
data-dispatch |
data-dispatch="actionName" |
Dispatches an event to the logic script (e.g., on('actionName', ...)). |
data-ref |
data-ref="myInput" |
Captures element into refs.myInput for imperative access. |
data-arg-* |
data-arg-id="item.id" |
Passes evaluated args to handlers via e.args. |
data-attr-* |
data-attr-aria-label=\"item.name\" |
Sets an attribute from an expression (use for aria-*, style, etc.). |
Notes:
data-classsupports multiple pairs separated by;(e.g.active:expr; muted:expr2) and class names containing:.data-listrequires a<template data-item>inside the list element (it can be nested inside wrappers).data-listsupports aliases viaalias in expr/alias of exprand nested lists.data-valueupdates state automatically for assignable paths; keepdata-dispatch-input/changefor side effects or custom validation.- There is no
data-styleordata-dispatch-stopbuilt in; usedata-attr-styleor handlers when needed.
Component styles are injected into <head> and apply globally. To keep styles reusable:
- Prefix selectors with the component tag name (e.g.
simple-counter .counter). - Wrap component rules in a CSS layer, e.g.
@layer components.simple-counter { ... }.
Use these options to keep DOM nodes stable under frequent updates:
- Keyed lists: add
data-list-key="item.id"to preserve list item DOM nodes across updates. - Static lists: add
data-list-once(ordata-list-static) when the list never changes. - No-op writes: state updates that don’t change values are ignored to avoid unnecessary renders.
If you need app-wide key handling, use a scoped global listener with cleanup and editable-target guards:
export default ({ onMount, onCleanup, self }) => {
const onKey = (e) => {
const path = e.composedPath ? e.composedPath() : [e.target];
const tag = e.target && e.target.tagName;
const isEditable = path.some(el => el && el.isContentEditable) ||
(tag && ["INPUT", "TEXTAREA", "SELECT"].includes(tag));
if (isEditable) return;
if (self && !path.includes(self)) return;
// handle key
};
document.addEventListener("keydown", onKey);
onCleanup(() => document.removeEventListener("keydown", onKey));
};Alternative: use a hidden input with data-dispatch-keydown and focus it on mount.
Example global state shape for timeline-based apps:
{
"project": {
"id": "proj-1",
"name": "Demo",
"tempo": 120,
"events": [
{ "id": "evt-1", "start": 0.0, "duration": 0.25, "note": "C4", "velocity": 0.9 }
]
}
}Use data-list-key="item.id" for events and keep id stable across updates.
Minimal pattern for persistence:
const STORAGE_KEY = "boredom-project";
export default ({ onMount, onCleanup, state }) => {
onMount(() => {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) state.project = JSON.parse(raw);
});
const save = () => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(state.project));
};
// call save() after edits, or debounce in onUpdate
};When using Audio or Canvas, update a simple state field to make behavior testable:
state.lastOp = { op: "noteOn", note: "C4", time: 1.25 }
The script must export a default function that receives the component context:
export default ({ on, onMount, onUpdate, onCleanup, self, state, local, refs }) => {
// on: Register action handlers
// onMount: Runs once after initial bindings
// onUpdate: Runs after every update (bindings run first)
// onCleanup: Runs on disconnect
// self: Reference to the component element
// state: The global reactive state proxy
// local: Instance-local reactive state (updates only this component)
// refs: Dictionary of elements with data-ref="name"
// 1. Initialize local state
local.inputValue = "";
on("my-event", ({ state, local, refs, self, e }) => {
// state: Mutable global state
// local: Mutable local state
// refs: Access DOM elements (e.g., refs.myInput.focus())
// self: Component element (light DOM)
// e: { event, dispatcher, args }
});
onMount(({ refs }) => {
// DOM-ready initialization
});
onUpdate(({ state, local, refs }) => {
// Custom DOM manipulation if needed
});
onCleanup(() => {
// Remove observers, timers, subscriptions
});
};Notes:
- Action handlers are component-scoped; a
data-dispatchin a child component must be handled by that child component's script.
This project uses Playwright to ensure the framework's stability across browsers.
npm install
npm testsrc/boreDOM.js: The core runtime (minified & documented).src/*.html: Example applications (Counter, TodoList, TicTacToe).e2e/*.spec.ts: End-to-end tests verifying reactivity and isolation.