Sneaking in real quick to thank you for your contributions and positive attitude you bring to the space.
fleahunter 2 days ago [-]
[dead]
amaury_bouchard 2 days ago [-]
Wow thanks a lot! That's very kind, I really appreciate it :)
gavmor 2 days ago [-]
I really like these sorts of frameworks from an architectural perspective, but what's the use-case? Maybe I'm too SPA-pilled, because to me all the fun of Web development is in providing really fluid, skeuomorphic experiences like those enabled by, eg pragmatic-drag-and-drop[0] or yjs[1].
I just struggle to envision what application benefits from the efficiency that this or htmx offer, but from neither the ultra-interactive, nor the ultra-collaborative. Maybe updating stock ticker prices? Fast-service back-of-house ticketing displays?
I would love to feel called to reach for this library.
Relying on server side as the source of truth inverts complexity. Suddenly 90% of the modern webs problems evaporate. Turns out building everything on top of a shadow dom in the client creates a boatload of unforced errors.
amaury_bouchard 2 days ago [-]
The sweet spot is everything between a static website and a full SPA, which is actually most of the web. Think e-commerce (filtering, cart updates, pagination), dashboards (refreshing metrics, notifications), admin interfaces (inline editing, form submissions), content-heavy sites (live search, comments, modals). Basically any app where the primary interaction is 'user does something, server responds with updated content'. For drag-and-drop kanban boards or collaborative editors, you're right, µJS is not the right tool. But those are a small fraction of what gets built every day. Most web apps are glorified CRUD interfaces, and for those, server-rendered HTML fragments are simpler, faster to develop, and easier to maintain than a full SPA.
qingcharles 2 days ago [-]
I love that drag/drop, BTW. Really nice. Gonna see how I can use that.
nattaylor 2 days ago [-]
Reminds me a little of htmz
htmz is a minimalist HTML microframework for creating interactive and modular web user interfaces with the familiar simplicity of plain HTML.
amaury_bouchard 2 days ago [-]
Yes, htmz is a great project! The core idea is similar, but htmz is intentionally more minimal (it's literally 166 bytes). µJS covers more ground: patch mode, SSE, DOM morphing, View Transitions, prefetch, polling, full HTTP verb support.
ok_dad 2 days ago [-]
Yay someone already mentioned my favorite “framework”!
htmz is a masterclass in simplicity. It’s gotta be the all time code golf winner.
oso2k 2 days ago [-]
This looks interesting. Thanks for sharing.
whiterock 2 days ago [-]
quite cool, but the browser history being messed up drives me bonkers. is that easy to fix?
lastdong 2 days ago [-]
You should be able to push to history using history.pushState(…) after loading a new page. There are probably more details needed around popState to handle back and forward navigation. I’m sure Claude will be able to assist here, but maybe too much complexity when other similar libraries may also manage history state.
amaury_bouchard 10 hours ago [-]
Yes, µJS manages browser history out of the box.
oso2k 2 days ago [-]
There’s several other (well) known examples of the use of mujs.
There’s Artifex’s interpreter from muPDF. It’s also the basis of several JS related projects: https://mujs.com/
And IIRC, there was a CommonJS library of the same name.
amaury_bouchard 2 days ago [-]
Thanks for the list! Yes, the name is used by several projects. Mine is the browser navigation one — hopefully the "µJS" name, the .org domain and the use case make it distinct enough.
zoezoezoezoe 10 hours ago [-]
"feel like a single-page app" except that its not and it will never be. I hate HTML-over-the-wire solutions to any problem. There is not a worse solution that has been more normalized than html-over-the-wire.
amaury_bouchard 10 hours ago [-]
Fair enough, it's clearly not the right tool for everyone. HTML-over-the-wire works well for server-rendered apps where the primary interaction is fetching and displaying content. For highly interactive UIs with complex client-side state, a proper SPA framework is the better choice. Different problems, different tools.
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.
zoezoezoezoe 4 hours ago [-]
idk. I'm just extremely not sold on the idea of having practically any interaction going over the wire unless absolutely necessary. Keeping everything in the browser for as long as is physically possible is what improves responsiveness, a single page application, once loaded, is not susceptible to spotty connections, dropping packets, a slow connection, whatever, like html-over-the-wire is.
I have been extremely bearish on html-over-the-wire solutions from the minute I saw and understood what HTMX was trying to achieve. In my eyes, the only way to truly achieve what I, and users, expect in a web page is with a SPA. I understand the hesitation to use heavier SPAs, but my hesitation to fetching html from the server after the page load to update the page is much larger. But I also do understand how html-over-the-wire provides a good middle ground between web 1.0 apps where basically every interaction reloaded the entire page, and web 2.0 apps that feel closer to an actual application rather than a website.
stanfordkid 2 days ago [-]
I'm curious to understand, why would you build your website this way vs. say jQuery. I've never really understood the HTMX ecosystem. Is this just to avoid javascript and replace that with html pages, id's and attributes? It feels like the DOM is a very clear abstraction and scripting is a more powerful way to manipulate it. What do people like or prefer about this approach and paradigm?
amaury_bouchard 2 days ago [-]
The target audience is developers who are efficient at generating HTML server-side using an MVC framework, a template engine, whatever they're comfortable with. Instead of building a JSON API and a JavaScript layer to consume it, you just return HTML fragments from your existing routes.
µ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.
freakynit 2 days ago [-]
What happens when a user lands directly on 2nd or 3rd level page and have not "jumped" from 1st level page?
amaury_bouchard 1 days ago [-]
Nothing special happens. µJS doesn't change anything for direct URL access. The server renders the full page as usual. µJS only intercepts navigation once it's initialized on the page. This is actually one of the advantages of this approach over a true SPA: every URL is a fully server-rendered page, so direct access, bookmarks, and sharing links all work out of the box.
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.
freakynit 20 hours ago [-]
Got it.. thanks..
vbernat 2 days ago [-]
The idea is that all the rendering is done server-side. So, the user always get a full page.
hwhshs 2 days ago [-]
I feel like the browser should just do the same thing and this not be needed. E.g. seemless reload, keep scroll and focus state etc.
freakynit 2 days ago [-]
Hmm, got it. Thanks..
logicprog 2 days ago [-]
I think the idea is that it's completely declarative, and the range of what you can do, being constrained to a set list of operations and targets, becomes clearer and easier to understand?
lofaszvanitt 2 days ago [-]
JS ecosystem is bonkers. Do not look for logic where there is none.
josephernest 2 days ago [-]
Nice project, always interesting to see HTMX-inspired frameworks.
If you want something even more minimalistic, I did Swap.js:
100 lines of code, handles AJAX navigation, browser history, custom listeners when
parts of DOM are swapped, etc.
Using it for a few production products and it works quite well!
amaury_bouchard 2 days ago [-]
Nice! 100 lines is impressive. µJS is a bit more feature-rich (patch mode, SSE, DOM morphing, View Transitions...) but if you need something truly minimal, Swap.js looks like a great option.
gaigalas 2 days ago [-]
I like the idea. DOM morphing is nice.
I've done this previously with morphdom to AJAXify a purely server-driven backoffice system in a company.
I would love something even smaller. No `mu-` attributes (just rely on `id`, `href`, `rel`, `rev` and standard HTML semantics).
Overall, I think old 2015-era microdata like RDFa and this approach would work very well. Instead of reinventing attributes, using a standard.
amaury_bouchard 2 days ago [-]
Thanks for the feedback! The RDFa `resource` attribute is an interesting angle I hadn't considered. That said, µJS's default behavior actually requires zero `mu-` attributes; it intercepts standard `href` links and `action` forms out of the box. The `mu-` attributes are only needed when you want to override the default behavior, use patch mode (updating multiple DOM fragments in a single request), or trigger requests from elements that are not links or forms. So in the simplest case, a single `mu.init()` call is all you need. That said, if you want something even more minimal, htmz takes that philosophy to the extreme.
0x20cowboy 2 days ago [-]
Not exactly what you’re saying, but a bit closer. With this library you set what css classes on the page are “hot”, it fetches the next page state and replaces that part of the page with the new state: https://github.com/robrohan/diffy
ohghiZai 2 days ago [-]
Would love to see a comparison with Datastar too
amaury_bouchard 2 days ago [-]
Thanks for the suggestion! I'm considering adding more libraries to the comparison page (Datastar and Unpoly are on my radar).
That said, µJS and Datastar have quite different philosophies. µJS is a lightweight AJAX navigation library (~5 KB); it intercepts links and forms, swaps fragments, and stays out of your way. There's no client-side state: your server renders HTML, µJS delivers it.
Datastar is more of a reactive hypermedia framework. It brings client-side signals (reactive state in HTML attributes, à la Alpine.js) and uses SSE as its primary transport: the server pushes updates rather than the client fetching them. It's a different mental model: Datastar manages state and reactivity, while µJS is purely about navigation and content replacement.
Both are small, zero-build-step, and attribute-driven, so the comparison is definitely interesting. I'll look into adding it!
amluto 1 days ago [-]
I’m curious: why does the default behavior do the magic replacement for absolute paths links but not for relative paths?
Also, perhaps the CDN script snippets in the getting started page should include the integrity attribute.
amaury_bouchard 1 days ago [-]
Good points, thanks!
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.
amluto 1 days ago [-]
I have a project that has ordinary internal links, but they’re all relative because the entire project can be based at a variable path.
If I were to adopt µJS, though, I would probably want to opt in on a per-link basis.
amaury_bouchard 1 days ago [-]
That's a valid use case. For opt-in per link, you can disable global link interception with `mu.init({ processLinks: false })` and then add `mu-url` explicitly to the links you want µJS to handle. For the variable base path scenario, the `urlPrefix` option in `mu.init()` might also help. It prepends a prefix to all fetched URLs.
That said, proper support for relative paths is on the roadmap.
scuff3d 2 days ago [-]
I don't do a lot of frontend work, but I'm glad to see more of this stuff popping up. Anything that combats the normal framework insanity is a good thing in my book. And this looks like a really cool idea. The default routing approach is an awesome idea, swapping the entire body by default is also really interesting. It seemed odd to me at first, but the more I thought about it the more it made sense.
amaury_bouchard 2 days ago [-]
Thanks! Yes, swapping the entire body by default felt like the right trade-off, it covers 90% of use cases with zero configuration, and you can always narrow it down to a specific fragment (or group of fragments) when needed.
katsura 2 days ago [-]
Quite impressive! I’ve been looking into Turbo alternatives so it showed up on my feed the right time.
One gripe with the name though is that I’m used to uTorrent’s and uws’s use of “u” for the “µ” character. So, my first guess would be to look for uJS to find this project, not muJS.
amaury_bouchard 2 days ago [-]
Thanks, looking forward to your feedback!
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.
jadbox 2 days ago [-]
What's the best way to handle having multiple frontend components respond to events from a single SSE connection from some parent node data provider? Ideally we do not what many SSE connections within a page for each component.. right? Then what's the best practice to handle that single SSE handler on the server and frontend? If that makes sense...
amaury_bouchard 2 days ago [-]
That's exactly what patch mode is designed for. You open a single SSE connection on a parent element with `mu-mode="patch"` and `mu-method="sse"`, and the server sends HTML fragments with `mu-patch-target` attributes pointing to whichever components need updating. One connection, multiple targets. The server decides which components to update on each event.
jadbox 1 days ago [-]
I love this- I'll give it a spin. I'd really want web developers to start making websites again that can run without javascript, but it can feel like a SPA if JS is available. This has always been 'possible' but, in practice, it's a massive pain. uJS and HtmX could help to be the polyfill solution to make this possible.
That said, I worry about Form elements being controlled by uJS to use a different REST method (like DELETE), and this would cause JS-free browsers to break.
amaury_bouchard 1 days ago [-]
Glad it clicks! On the progressive enhancement point, µJS is designed exactly with that philosophy in mind: standard links and forms work without JS, µJS just enhances them.
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
Could you please add all sources as tabs? For example in Form (GET) I would really like to see /demos/search-results.html and the same goes for other examples.
Thanks!
amaury_bouchard 10 minutes ago [-]
Good news, I've just added the server-side source as a tab in the Playground. Thanks again for the suggestion!
amaury_bouchard 2 days ago [-]
Good point, thanks! That would definitely make the examples clearer. I'll keep it in mind.
_HMCB_ 2 days ago [-]
Layman’s question: How to avoid the page jumping back to the top in the Playground examples?
amaury_bouchard 1 days ago [-]
No worries! You can prevent the page from scrolling to the top by adding `mu-scroll="false"` to the link or form triggering the request. You can also disable it globally with `mu.init({ scroll: false })`.
In the Playground, the scroll-to-top behavior is intentiona, it's there to illustrate that feature.
ranger_danger 2 days ago [-]
Does it automatically parse JSON responses from servers into objects? This is my one big gripe about htmx, even though the devs and other users keep telling me I shouldn't want that as a feature and that it "doesn't make sense".
Sorry if I need to use existing APIs I cannot change.
WesolyKubeczek 2 days ago [-]
I came to a conclusion that when you have an SPA with JSON-spitting backend where you cannot make the backend spit out chunks of HTML, htmx and similar libraries/frameworks are not suitable. They are suitable if you already have a multi-page application like we used to in 2006, or if you design it from the ground up.
amaury_bouchard 2 days ago [-]
µJS is HTML-over-the-wire, so it doesn't parse JSON responses natively (same limitation as HTMX). That said, you can work around it using the `mu:before-render` event, which lets you intercept the raw response before it hits the DOM and handle it however you want. It's not as clean as native JSON support, I'll admit. If your use case is primarily consuming existing JSON APIs, µJS (and HTMX) are probably not the right fit, they're designed around server-rendered HTML fragments.
scuff3d 2 days ago [-]
They're right through, the entire point of HTMX is to use "HTML as the Engine of Application State". Shipping JSON to the front end to be rednered is antithetical to that.
As someone else mentioned, having your own server backend act as an intermediary between your front end and the API that serves JSON is probably the most straightforward solution to keep everything HTMX-y.
williamcotton 2 days ago [-]
Browser -> your server route -> server calls API -> server renders HTML -> htmx swaps it?
lioeters 2 days ago [-]
Looks useful! I skimmed through the docs and had a question.
Is there a mechanism for loading HTML partials that require additional style or script file? And possibly a way to trigger a JS action when loaded? For example, loading an image gallery.
amaury_bouchard 2 days ago [-]
Yes! `<script>` and `<style>` tags found in the fetched content are processed on the fly, as long as they haven't been loaded already. So you can include a gallery-specific stylesheet or script directly in the partial, and µJS will inject and execute them automatically. For triggering a JS action after rendering, the `mu:after-render` event is the right hook.
lioeters 2 days ago [-]
I see, simple! Thanks, I look forward to exploring uJS further.
heddycrow 2 days ago [-]
It's about time browsers start supporting something like this natively. Fingers crossed.
I'll be checking this out. Any chance you (or anyone) has had a run with this lib + web components? I'd love to hear about it.
2 days ago [-]
amaury_bouchard 2 days ago [-]
I haven't tried it personally, but I don't see any reason why they wouldn't work well together. µJS manipulates the DOM via fetch and swapping, while web components live in the DOM like any other element. The only thing to be aware of is that if µJS replaces a fragment containing a web component, connectedCallback will fire again, which is the expected behavior. Would love to hear your feedback if you give it a try!
heddycrow 2 days ago [-]
I certainly will. I already have this on my stack. Will feedback + thanks!
pjax is actually listed as an inspiration in the README. It's a great project, but it hasn't been maintained since 2018 and requires jQuery. µJS is dependency-free and covers a much broader feature set.
Indeed! While the names are similar, µJS (mujs.org) and MuJS (mujs.com) are completely different projects. The former is a browser navigation library (a lightweight alternative to HTMX and Turbo), while the latter is an embeddable JavaScript interpreter written in C, developed by Artifex for MuPDF. Sorry for the confusion!
panny 12 hours ago [-]
I tried this out today and it doesn't seem to work for me, because it only considers relative urls to be "internal." If the urls are full urls (https://domain:port/...) then it won't work. It seems like it should be able to use window.location to work out that these full urls are also "internal" as well.
amaury_bouchard 6 hours ago [-]
Thanks for the report! Using fully absolute URLs for internal links is not a very common pattern, and this hadn't been raised before. That said, it's a valid and interesting use case, I'll add it to the roadmap. In the meantime, using root-relative URLs (starting with /) is the recommended approach.
reverseblade2 2 days ago [-]
Another library ignores shadow dom.
As of now you don't need any frameworks for this. The new command and navigation apis, would just do the same trick.
amaury_bouchard 1 days ago [-]
Fair point on shadow DOM. That said, µJS targets server-rendered HTML workflows, where pages are plain HTML generated by a backend framework or template engine. In that context, shadow DOM is rarely used, so this limitation doesn't affect the typical µJS user. If you're building heavily component-based UIs with Web Components, µJS is probably not the right tool anyway. A proper JS framework would serve you better.
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.
poobrains 2 days ago [-]
Which new command and navigation apis are you referring to?
reverseblade2 1 days ago [-]
Google is your friend
swaminarayan 2 days ago [-]
At ~5KB gzipped this is impressively small. What were the biggest features you deliberately left out to keep the size that low?
amaury_bouchard 1 days ago [-]
Thanks! The short answer: µJS doesn't try to be a DSL in HTML attributes.
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.
This is bad advice to a new FLOSS project that wants to have users. Avoiding GitHub with its user base (meaning issues and discussions), search, project topics (tags), trending repository lists, etc. will make a fledgling project even less likely to gain adoption.
A better thing to suggest is to use multiple forges, including GitHub, and mirror your projects across them. This way you will have exposure and options; you won't be as tied to any one forge.
majorchord 2 days ago [-]
Hard disagree, multiple forges does not solve the problem of being unable to opt-out of AI training from your code.
networked 2 days ago [-]
If that is your problem with GitHub, then I agree, you should avoid GitHub, though someone can still mirror your repository there. I assume most new FLOSS projects that want to have users don't consider it a dealbreaker.
satvikpendem 2 days ago [-]
If your code is in any way public, it will be trained on. That ship has already sailed.
hombre_fatal 2 days ago [-]
If your problem is with your code appearing in training data, then you cannot release your code anywhere.
That link you provided only points out GitHub has integrated "create pull request with Copilot" that you can't opt out of. Since anyone can create a pull request with any agent, and probably is, that's a pretty dated complaint.
Frankly not very compelling reasons to ditch the most popular forge if you value other people using/contributing to your project at all.
amaury_bouchard 2 days ago [-]
Thanks for the link. I'm aware of the debate around GitHub. For now it's where µJS lives, but noted.
satvikpendem 2 days ago [-]
It is not worth taking seriously people who have very niche complaints that don't even use your software, believe me. It's the same as some people here who complain that they disable JS and some web app doesn't work, like yeah, no shit. Catering to these people will just frustrate you and make you more likely to give up creating the product.
i have added it to the htmx alternatives page:
https://htmx.org/essays/alternatives/#ujs
I just struggle to envision what application benefits from the efficiency that this or htmx offer, but from neither the ultra-interactive, nor the ultra-collaborative. Maybe updating stock ticker prices? Fast-service back-of-house ticketing displays?
I would love to feel called to reach for this library.
0. https://github.com/atlassian/pragmatic-drag-and-drop
1. https://github.com/yjs/yjs
Relying on server side as the source of truth inverts complexity. Suddenly 90% of the modern webs problems evaporate. Turns out building everything on top of a shadow dom in the client creates a boatload of unforced errors.
htmz is a minimalist HTML microframework for creating interactive and modular web user interfaces with the familiar simplicity of plain HTML.
htmz is a masterclass in simplicity. It’s gotta be the all time code golf winner.
There’s Artifex’s interpreter from muPDF. It’s also the basis of several JS related projects: https://mujs.com/
There’s also a lesser known interpreter: https://github.com/ccxvii/mujs
And IIRC, there was a CommonJS library of the same name.
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.
I have been extremely bearish on html-over-the-wire solutions from the minute I saw and understood what HTMX was trying to achieve. In my eyes, the only way to truly achieve what I, and users, expect in a web page is with a SPA. I understand the hesitation to use heavier SPAs, but my hesitation to fetching html from the server after the page load to update the page is much larger. But I also do understand how html-over-the-wire provides a good middle ground between web 1.0 apps where basically every interaction reloaded the entire page, and web 2.0 apps that feel closer to an actual application rather than a website.
µ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.
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.
If you want something even more minimalistic, I did Swap.js: 100 lines of code, handles AJAX navigation, browser history, custom listeners when parts of DOM are swapped, etc.
https://github.com/josephernest/Swap.js
Using it for a few production products and it works quite well!
I've done this previously with morphdom to AJAXify a purely server-driven backoffice system in a company.
I would love something even smaller. No `mu-` attributes (just rely on `id`, `href`, `rel`, `rev` and standard HTML semantics).
There's a nice `resource` attribute in RDFa which makes a lot of sense for these kinds of things: https://www.w3.org/TR/rdfa-lite/#h-resource
Overall, I think old 2015-era microdata like RDFa and this approach would work very well. Instead of reinventing attributes, using a standard.
That said, µJS and Datastar have quite different philosophies. µJS is a lightweight AJAX navigation library (~5 KB); it intercepts links and forms, swaps fragments, and stays out of your way. There's no client-side state: your server renders HTML, µJS delivers it.
Datastar is more of a reactive hypermedia framework. It brings client-side signals (reactive state in HTML attributes, à la Alpine.js) and uses SSE as its primary transport: the server pushes updates rather than the client fetching them. It's a different mental model: Datastar manages state and reactivity, while µJS is purely about navigation and content replacement.
Both are small, zero-build-step, and attribute-driven, so the comparison is definitely interesting. I'll look into adding it!
Also, perhaps the CDN script snippets in the getting started page should include the integrity attribute.
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.
If I were to adopt µJS, though, I would probably want to opt in on a per-link basis.
That said, proper support for relative paths is on the roadmap.
One gripe with the name though is that I’m used to uTorrent’s and uws’s use of “u” for the “µ” character. So, my first guess would be to look for uJS to find this project, not muJS.
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.
That said, I worry about Form elements being controlled by uJS to use a different REST method (like DELETE), and this would cause JS-free browsers to break.
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
Could you please add all sources as tabs? For example in Form (GET) I would really like to see /demos/search-results.html and the same goes for other examples.
Thanks!
In the Playground, the scroll-to-top behavior is intentiona, it's there to illustrate that feature.
Sorry if I need to use existing APIs I cannot change.
As someone else mentioned, having your own server backend act as an intermediary between your front end and the API that serves JSON is probably the most straightforward solution to keep everything HTMX-y.
Is there a mechanism for loading HTML partials that require additional style or script file? And possibly a way to trigger a JS action when loaded? For example, loading an image gallery.
I'll be checking this out. Any chance you (or anyone) has had a run with this lib + web components? I'd love to hear about it.
As of now you don't need any frameworks for this. The new command and navigation apis, would just do the same trick.
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.
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.
A better thing to suggest is to use multiple forges, including GitHub, and mirror your projects across them. This way you will have exposure and options; you won't be as tied to any one forge.
That link you provided only points out GitHub has integrated "create pull request with Copilot" that you can't opt out of. Since anyone can create a pull request with any agent, and probably is, that's a pretty dated complaint.
Frankly not very compelling reasons to ditch the most popular forge if you value other people using/contributing to your project at all.