69 karma · joined March 6, 2026
My personal work spans software, games, and tools (all published under open source licenses): - Temma (https://www.temma.net): PHP MVC framework, easier than the big frameworks, more useful than the micro-frameworks - µJS (https://mujs.org): Lightweight AJAX navigation library, 5KB alternative to Turbo and htmx - µCSS (https://mucus.org): Full-featured CSS framework, based on PicoCSS - Arkiv (https://github.com/Digicreon/Arkiv): Easy-to-use backup and archive tool - Dispak (https://github.com/Digicreon/Dispak): Extensible tool for managing Git repositories and project deployment - Rolis (https://www.rolis.net): A list of open-sourced tabletop role playing games, and multi-engine open-source scenarios - Pandocreon Menhir (http://www.pandocreon.com/jeux/menhir/): Award-winning boardgame
µJS now resolves URLs using the native URL constructor and compares origin against window.location.origin. This means all same-origin URLs are correctly recognized as internal: - Absolute URLs: https://domain:port/page - Relative URLs: ../page.html, page.html - Local paths: /page (already worked)
Hash-only links (#section) are intentionally left to the browser for native anchor scrolling.
That said, the vast majority of websites are purely transactional: click a link, load a page; submit a form, load a page. For those, there's little reason to add a full frontend framework. HTML-over-the-wire can improve responsiveness without adding complexity.
That said, proper support for relative paths is on the roadmap.
Your concern about `mu-method="delete"` is valid. Without JS, the browser will fall back to the form's native method (usually GET or POST). The classic solution is the `_method` hidden field pattern: the form submits POST with a `_method=DELETE` field, and the server reads that field to route the request correctly. µJS doesn't need to implement anything special for this, it's a server-side convention
On relative paths: the current behavior is intentional for simplicity. µJS checks whether the URL starts with a slash (but not a double slash) to identify internal links. No one has reported this as an issue so far, but it's a valid feature request and I'll keep it in mind for a future version.
On the integrity attribute: the reason it was missing is that the library was evolving quickly and the hash would have changed with every release. Now that it's stable, I'll add it.
Here's what I deliberately left out compared to Turbo (~25KB gzip) and htmx (~16KB gzip):
- No client-side template/extension system. htmx has a full extension API, header-based control flow (HX-Trigger, HX-Redirect, HX-Reswap...), and dozens of response headers the server can use to command the client. µJS has 5 custom headers total; the server returns HTML, the client renders it.
- No CSS selector-based targeting in attributes. htmx lets you write hx-target="closest tr", hx-target="find .result", hx-target="next div" with a mini query language. µJS uses plain CSS selectors only (mu-target="#id"), no traversal keywords.
- No built-in transition/animation classes. htmx adds/removes CSS classes during swaps (htmx-settling, htmx-swapping, htmx-added) with configurable timing. Turbo has turbo-preview, transition classes, etc. µJS defers to the native View Transitions API: zero animation code in the library, and it falls back silently.
- No client-side cache or snapshots. Turbo keeps a page cache for "instant" back-button rendering and manages preview snapshots. µJS stores only scroll position in history.state and lets the browser's own cache + fetch() handle the rest.
- Patch mode simpler than Turbo Frames. Turbo has <turbo-frame>, a custom element with lazy-loading, src rewriting, and frame-scoped navigation. µJS handles multi-fragment updates via patch mode (mu-mode="patch") with regular HTML elements. No custom elements, no frame concept. It's simpler but it works perfectly.
- No request queuing or throttling. htmx has hx-sync with queuing strategies (queue, drop, abort, replace). µJS simply aborts the previous in-flight request. One request at a time per navigation.
- No form serialization formats. htmx supports JSON encoding (hx-encoding), nested object serialization, etc. µJS uses the browser's native FormData for POST and URL params for GET. That's it.
- No JavaScript API surface to speak of. htmx exposes htmx.ajax(), htmx.process(), htmx.trigger(), event details, etc. µJS exposes mu.init(), mu.fetch(), mu.render(), and a few setters and a few events. The goal is that you shouldn't need the JS API — the HTML attributes should be enough.
What I kept: the features that cover 90% of real-world use cases. AJAX navigation, 8 injection modes, multi-fragment patch, morphing (via Idiomorph), SSE, prefetch, forms with all HTTP verbs, triggers with debounce/polling, progress bar, history/scroll management. Just without the layers of abstraction around them.
The philosophy: if the browser already does it (View Transitions, FormData, fetch, AbortController, history API), don't reimplement it.
On the native APIs: yes, the Navigation API and Speculation Rules are exciting, but browser support is still uneven. µJS works today across all modern browsers without any configuration. That said, I agree the gap will narrow over time.
In the Playground, the scroll-to-top behavior is intentiona, it's there to illustrate that feature.
Let's take an example. Say we have a website with 3 pages:
- Homepage "website.com/"
- Products "website.com/products"
- About "website.com/about"
Your browser can load any of these pages directly, as usual. But when you are on the homepage (for example) and you click on the "About" link, µJS load the "/about" URL, and replace the current page's `<body>` tag with the fetched page's one.
It's that simple.
I deliberately chose the phonetic spelling 'mu' of the 'µ' greek letter, rather than the typographic approximation 'u'. The 'u' convention comes from ASCII limitations; when µ wasn't available, 'u' was used as a fallback. Since we're well past those limitations, 'mu' felt more accurate and more original.
µJS handles the AJAX call and swaps the fragment in the DOM. No JavaScript to write, no state to manage client-side. jQuery is more powerful and flexible, but that flexibility comes with complexity: you have to write the fetch call, handle the response, find the right DOM node, update it.
µJS does all of that declaratively via HTML attributes. It's not for every use case — highly interactive apps (think Google Maps or a rich text editor) still need proper JavaScript. But for the vast majority of web pages, server-rendered HTML fragments are simpler, faster to develop, and easier to maintain.