Faster than VDOM. No Build Step. Use the Platform!
JJ is a lightweight, no-transpilation library for modern web development. What You Write Is What Is Run (WYWIWIR).
JJ complements browser-native capabilities instead of replacing them with a thick abstraction layer. It is designed for moderate to advanced web developers and AI agents that want fluent DOM helpers while staying close to standards.
CDN:
Import map: Add this to your HTML:
<script type="importmap">
{
"jj": "https://cdn.jsdelivr.net/npm/jj/lib/bundle.min.js"
// Or a specific version
"jj": "https://cdn.jsdelivr.net/npm/jj@2/lib/bundle.min.js
}
</script>
And then:
import { JJHE } from 'jj'
CDN:
import { JJHE } from 'https://cdn.jsdelivr.net/npm/jj/lib/bundle.min.js'
NPM:
npm i jj
import { JJHE } from 'jj'
JJHE.create('div')
.addClass('card')
.setText('Hello World!')
.on('click', () => console.log('Hi'))
Use namespace-aware wrappers for non-HTML elements:
import { JJSE, JJME } from 'jj'
const icon = JJSE.create('svg').setAttr('viewBox', '0 0 24 24')
const formula = JJME.create('math').addChild(JJME.create('mi').setText('x'))
JJ is not a reactive framework, template compiler, or state-management system.
The guides intentionally include verified MDN links so you can move between JJ helpers and underlying browser APIs.
Batch setter helpers use plural methods:
setAttrs({...})setAriaAttrs({...})setDataAttrs({...})setClasses({...})ARIA helpers use explicit singular naming:
getAriaAttr(name)hasAriaAttr(name)setAriaAttr(name, value)setAriaAttrs({...})rmAriaAttr(...names)rmAriaAttrs(names)Dataset helpers use explicit singular/plural naming:
getDataAttr(name)hasDataAttr(name)setDataAttr(name, value)setDataAttrs({...})rmDataAttr(...names)rmDataAttrs(names)Use defineComponent(name, constructor, options?) and expose a static defined promise on each custom element class:
import { defineComponent } from 'jj'
export class MyWidget extends HTMLElement {
static defined = defineComponent('my-widget', MyWidget)
}
await MyWidget.defined
defineComponent() resolves a Promise<boolean>:
false: this call registered the component.true: it was already registered with the same constructor.This explicit definition step is important for reliability. If markup is parsed before the element is defined, upgrades can race with rendering and produce flaky behavior.
π Visit the full site with tutorials, examples, and API docs
JJ is designed for AI-assisted development. Install the skill for intelligent code suggestions:
npx skills add alexewerlof/jj
Once installed, AI agents (GitHub Copilot, Cursor, Claude Code, Windsurf, etc.) will:
.ref for native DOM accessThe skill definition (SKILL.md) is also included in the npm package at node_modules/jj/SKILL.md.
The entire public API is tested thoroughly.
Tests live in the test/ folder and mirror the source filenames (e.g., test/JJE.test.ts for src/JJE.ts, test/JJME.test.ts for src/wrappers/JJME.ts) while importing the target from ./src/index.js.
Run tests with:
npm test
MIT
Made in Sweden πΈπͺ by Alex EwerlΓΆf