The HTML-first, zero-build, single-page, client-side application, micro-framework.
MinTML extends standard HTML instead of replacing it — routing, templating, and role-based visibility that get out of the way. Remove new MinTML() from your page and everything (links, forms, layout) should still just work.
Write clean, pure HTML, modern CSS, and vanilla JavaScript — standards-compliant, semantic markup that runs natively in the browser, with no transpiler standing between your code and the platform. The way the web was intended.
No build step required to use it, no config, no virtual DOM. Just HTML with a few data-* attributes and a small script tag.
npm install @mintml/mintmlimport MinTML from '@mintml/mintml';<script type="module">
import MinTML from 'https://cdn.jsdelivr.net/npm/@mintml/mintml/dist/mintml.js';
</script>A minified build is also published at dist/mintml.min.js (about 4.4kB, ~1.9kB gzipped) — swap the path above for dist/mintml.min.js in production if you want the smaller payload:
<script type="module">
import MinTML from 'https://cdn.jsdelivr.net/npm/@mintml/mintml/dist/mintml.min.js';
</script><header>
<nav>
<a href="#">Home</a>
<a href="#about">About</a>
</nav>
</header>
<main>
<section id="home" data-page>Home content</section>
<section id="about" data-page>About content</section>
</main>
<script type="module">
import MinTML from '@mintml/mintml';
const app = new MinTML();
</script>Every [data-page] section is a "page." Hash links (#about) toggle which one is visible — no router config required. If you strip out the <script> tag, the page is still valid, readable HTML; it just won't switch sections automatically.
- Hash-based page routing — mark sections with
data-page; navigation is driven byhref="#id"links andwindow.location.hash. - 404 handling — if no matching page exists for the current hash, an auto-generated (or your own) error page is shown.
data-beforeguards — intercept a link or form submission before it navigates, run async logic (validation, confirmation, etc.), and decide whether to proceed:app.before('confirmDelete', async ({ element, payload }) => { return confirm('Are you sure?'); });
<a href="#done" data-before="confirmDelete">Delete</a>
- Role-based visibility — elements with
data-show="admin,editor"are shown/hidden based on the app's current role:const app = new MinTML({ roles: ['guest', 'admin'] }); app.role = 'admin';
- Lightweight templating —
<template>elements are read once and removed from the DOM; render them anywhere with variable substitution:<template id="card">Hello {{name}}</template>
el.innerHTML = app.render('card', { name: 'Todd' });
- DOM helpers —
$,$$, and$idare exported as short wrappers aroundquerySelector,querySelectorAll, andgetElementById. - Form serialization —
form2Object(form)turns a submitted<form>into a plain object, grouping repeated fields (checkboxes, multi-selects) into arrays automatically.
| Export | Description |
|---|---|
MinTML (default) |
Main class. new MinTML({ roles, errorPageId }) |
.navigate(id) |
Navigate to a page by id |
.refresh() |
Re-run routing for the current hash |
.before(name, fn) |
Register a guard used by data-before="name" |
.role / .roles |
Get/set the current role and configured roles |
.render(templateIdOrString, data) |
Render a <template> with {{key}} substitution |
$(selector) |
document.querySelector shorthand |
$$(selector) |
document.querySelectorAll shorthand, returns an array |
$id(id) |
document.getElementById shorthand |
form2Object(form) |
Serialize a <form> element into a plain object |
HTML is the foundation, not an afterthought. A MinTML page should degrade gracefully — if you delete the script tag, you still have a working, semantic HTML document.
MinTML encourages standards-compliant, semantic markup: clean, pure HTML, modern CSS, and vanilla JavaScript that run natively in the browser — no transpiler, no compiler, no build step standing between your code and the platform. The micro-framework adds behavior on top of markup you'd write anyway; it doesn't ask you to learn a templating DSL, run a bundler, or restructure your project around it. This is the way the web was intended.
npm install # installs esbuild, used only to produce the dist build
npm run build # regenerates dist/mintml.js and dist/mintml.min.js from public/mintml.jsThe dist/ build regenerates automatically before every npm publish (via prepublishOnly), so you never have to remember to run it by hand. dist/ is gitignored — it's fully derived from public/mintml.js, so there's nothing there to hand-edit or commit.
MIT © Todd Kingham