avatarJason Knight

Summary

The author of the provided content vehemently criticizes HTMX, a JavaScript framework, for promoting outdated and inaccessible web development practices, lack of understanding of fundamental web technologies, and for contributing to the proliferation of bloated, insecure, and poorly performing websites.

Abstract

The author expresses a strong disapproval of HTMX, labeling it as a regressive step in web development. They argue that the framework's creators lack a basic understanding of HTML, CSS, and JavaScript, as evidenced by their disregard for web standards, accessibility, and performance best practices. The critique extends to the framework's documentation and examples, which are riddled with inaccessible markup, redundant scripting, and security vulnerabilities. The author emphasizes that the use of HTMX not only undermines the principles of the web but also exposes a broader issue within the development community: the tendency to create frameworks without a deep understanding of the technologies they aim to enhance or replace.

Opinions

  • The author believes that the creators of HTMX, by integrating AJAX, CSS Transitions, WebSockets, and Server Sent Events directly into HTML, are ignoring the separation of concerns principle, which is crucial for maintainable and accessible web development.
  • The author criticizes the use of made-up attributes and scripting-only behaviors in the markup, comparing HTMX's approach to the outdated practices of the mid-1990s.
  • They point out that HTMX's examples and documentation fail to provide fallbacks for users with JavaScript disabled, which is a fundamental accessibility oversight.
  • The author takes issue with HTMX's claim of improving user interfaces, stating that it instead leads to bloated code and a disregard for users on non-visual user agents.
  • The critique includes the observation that HTMX's website itself is poorly coded, lacking DOCTYPE, media targets for CSS, and proper use of HTML elements, which reflects the developers' incompetence.
  • The author condemns the use of tables for layout in the HTMX website, a practice long abandoned in modern web design.
  • They also highlight the use of inline styles and the absence of meaningful alt text for images, further undermining accessibility and best practices.
  • The JavaScript code within HTMX is described as outdated and inefficient, with examples such as reinventing Array.forEach and using var instead of let or const.
  • The author suggests that the creators of HTMX are unqualified to be making a framework, as their code exhibits a lack of understanding of JavaScript, DOM manipulation, and security concerns, particularly regarding XSS exploits.
  • The overall sentiment is that HTMX is an unnecessary and harmful framework that perpetuates ignorance, incompetence, and ineptitude in web development, and that developers should focus on mastering core web technologies instead of relying on such frameworks.

HTMX, The “Framework Stupid” Gets Dialed Up To Eleven!

I have said for ages that the people who CREATE frameworks are generally are ignorant of the most basic concepts behind web development. They don’t know or understand HTML and semantic markup; they fail to grasp why the separation of concerns between HTML, CSS, and JS exists; they never once consider media targets, accessibility, HTTP parallelism, or any other meaningful metric of quality website development.

Basically they put their ignorance on full display, then attribute all sorts of unfounded merits to whatever they’ve done hoping that everyone else pats them on the back for it.

And it’s scary how many people will pat them on the back and say “well done” for these monuments to the ugliest of mid 1990’s development practices.

That lack of understanding is why they build alternatives. They either never learned, refused to learn, or didn’t find out where or even why things are done a certain way, and go diving to say “i can do better” before they even know what “better” is… much less have the ability to recognize how badly they are screwing themselves, and everyone else who gets suckered into thinking the nonsense is magically “easier”, “better”, or any of the other glittering generalities and propaganda driven bunko being peddled.

And HTMLX has all that in spades.

It’s amazing how many nose-breathing idiots thing “in spades” is a racist term.

Their “Motivation” Was Fueled By Ignorance.

You look at their broken inaccessible website — I’ll get to that more in a bit — starts out trying to explain why they built it. The very first actual content sentence?

htmx gives you access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext

Right off the bat tells us they’ve got Failwind / Bootcrap levels of “screw the users, screw caching, screw parallelism, screw the separation of concerns!”. Since scripting only behaviors are … scripting only, they do not belong in the HTML in the first place in all but the rarest of corner cases. They are literally saying to slop made up attributes into the markup for scripting recreating everything that was wrong with wuck-fittery like onevent, or even the outdated outmoded long deprecated appearance attributes.

It is literally to JavaScript what Failwind and Bootcrap are to CSS. That is not a compliment!

They then go on to list several “reasons” they created this, and that’s where the lack of understanding gets put on display:

Why should only and rm> be able to make HTTP requests?

Because for accessibility reasons swapping in/out content is rubbish for users on things like screen readers and braille readers. You’re also making life harder for search. In fact, the entire reason we have <button type=”button”> (remember, the default is type=”submit) is so that we can distinguish between scripting-only and non-scripted. So that we have *SHOCK* rules that user-agents can know ahead of time so it knows what to do for something more than the “screen and screw everyone else” attitude.

Why should only click & submit events trigger them?

Because that is the simplest way for interfaces to work when interacting with all sorts of user-agents. What the devil other types of triggers would you have for user-interaction that wouldn’t be too complex and inherently “scripting only” to have zero business in the HTML?

Why should only GET & POST methods be available?

Possibly because CREATE, DELETE, PUT, and so forth are really pointless for anything that isn’t server-to-server communication, and could in fact also be security risks… which last I checked is why you don’t see a lot of built-in support for them in languages like PHP. There’s $_GET, there’s $_POST, there’s even $_REQUEST combining the two. There is no $_DELETE.

For good reason. I mean FFS last I knew “CREATE” is supposed to create a new file overwriting the current HTML. “DELETE” is for deleting the current HTML, not just a “record”.

Is that really the intent?

Why should you only be able to replace the entire screen?

So that you can hotlink easily to each of your pages. So that users on non-visual UA’s have more of a clue of what your code is doing. So that you’re not jumping through batshit crazy hoops bloating out the markup.

I know, I know… all things this current age of “let’s use 200k of HTML to do 10k’s job, 500k of CSS to do 48k’s job, and 2 megabytes of JS where none is eeven warranted” mean absolutely nothing to the rank and file poseurs and frauds making up so-called web development “experts”.

These are not “arbitrary constraints” unless you literally don’t give a flying purple fish about anything more than scripting enabled screen media users.

Even their first example:

<script src="https://unpkg.com/[email protected]"></script>
  <!-- have a button POST a click via AJAX -->
  <button hx-post="/clicked" hx-swap="outerHTML">
    Click Me
  </button>

Script before the DOM is built likely meaning he’s having to screw with DOMContentReady or onload, made up attributes for scripting only behaviors (what data- exists for), but worst of all? A BUTTON WITH SCRIPTING ONLY BEHAVIOR STATIC IN THE MARKUP!

Meaning you’re serving it to users with scripting off/blocked/disabled. And of course nowhere in any of their examples do we see a single

But Let’s Look At “Real” Examples

Let’s just pick one at random here. https://htmx.org/examples/lazy-load/

<div hx-get="/graph" hx-trigger="load">
  <img  alt="Result loading..." class="htmx-indicator" width="150" src="/img/bars.svg"/>
</div>

Yeah… DIV for nothing static in the markup (should probably be an inline-block SPAN generated by the script), but worst of all… where’s the actual loading=”lazy” or anything to stop the image from loading? THEIR EXAMPLE DOESN’T EVEN WORK HERE! (at least not in Vivaldi or FF). It’s an IMG tag with a src=””, it’s gonna start loading before your scripting can even intercept it. That’s why pre loading=”lazy” scripts used a placeholder image and stored the actual image URI in “data-” or abused other ways of storing it. (like putting it in title, which was just… bad)

Much less does “Result Loading” REALLY describe what the image is of? Remember ALT is the fallback text for when the image doesn’t load or something like your scripttardery screws up!

Let’s grab another one: https://htmx.org/examples/progress-bar/

<div hx-target="this" hx-swap="outerHTML">
  <h3>Start Progress</h3>
  <button class="btn" hx-post="/start">
            Start Job
  </button>
</div>

Right off the bat it’s a submit button… DIV possibly for nothing, but… progress.

Don’t we have a TAG for progress?

Which is why the code it gets turned into:

<div hx-trigger="done" hx-get="/job" hx-swap="outerHTML" hx-target="this">
  <h3 role="status" id="pblabel" tabindex="-1" autofocus>Running</h3>

  <div
    hx-get="/job/progress"
    hx-trigger="every 600ms"
    hx-target="this"
    hx-swap="innerHTML">
    <div class="progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-labelledby="pblabel">
      <div id="pb" class="progress-bar" style="width:0%">
    </div>
  </div>
</div>

On top of being DIV soup rubbish, with tabindex on a non-focusable element (FFS LEARN HTML!!!), aria role crap because they’re using the wrong markup… and thanks to all that telling accessibility to sod off.

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress

Again this is 2023, not 2008.

Good rule of thumb, if you have to say role=”” it is entirely likely you’re using the wrong tags / elements!

How about another? https://htmx.org/examples/click-to-load/

<tr id="replaceMe">
  <td colspan="3">
    <button class='btn' hx-get="/contacts/?page=2"
                        hx-target="#replaceMe"
                        hx-swap="outerHTML">
         Load More Agents... <img class="htmx-indicator" src="/img/bars.svg">
    </button>
  </td>
</tr>

hard coded to the ID instead of using Element.closest(), static image inn the markup with no ALT text, class=”btn” because thank you Captain Obvious, and of course no scripting off fallback because they used button and scripting instead of intercepting an anchor, doing a Event.preventDefault(), and then using “get” data to tell the server to send just the data and not a page load. (which would be the graceful degradation approach) Again, reeking of not only not knowing basic HTML, but not giving a damn about scripting off fallbacks or how confusing their non-functional gibberish would be to non-script or non-screen users.

Even The Website Shows They Know Nothing About HTML Or CSS

A lot of people will say “oh, it’s just the website”. The website for a project should be a reflection of the competence of its developers. If you cannot write simple clean accessible semantic HTML for a page that simple, how am I or anyone else supposed to trust you with the intricacies of JavaScript?

You can tell a lot about a development project by looking at the code of their site. So let’s go to their home page and “break it down” becuase it’s clear, well… they don’t know HTML.

<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
     <title>&lt;&#x2F;&gt; htmx - high power tools for html</title>
    <link rel="canonical" href="https://htmx.org/">
    
    <link rel="alternate" type="application/atom+xml" title="Sitewide Atom feed" href="/atom.xml">
    <link rel="stylesheet" href="/css/site.css">
    <script src="/js/htmx.js"></script>
    <script src="/js/class-tools.js"></script>
    <script src="/js/preload.js"></script>
    <script src="/js/_hyperscript.js"></script>
    <meta name="generator" content="Zola v.TODO">
</head>

No DOCTYPE, no media targets for what’s clearly screen only CSS, no attempt at reducing handshakes, no preloads, and worst of all blocking scripts in the HEAD without a “defer” in sight.

And since those scripts will run before the DOM is parsed?

 getDocument().addEventListener('DOMContentLoaded', function() {

Where if the scripts were loaded right before they wouldn’t have to pull that stunt.

I don’t know what “Zola v. TODO” is, but if that’s what’s making markup like this? /FAIL/ hard.

We get into the

<body hx-ext="class-tools, preload">

<div class="top-nav">
    <div class="c">
        <div class="menu">
            <div class="logo-wrapper">
                <a href="/" class="logo light">&lt;<b>/</b>&gt; htm<b>x</b></a>
                <svg _="on click toggle .show on #nav" class="hamburger" viewBox="0 0 100 80" width="25" height="25" style="margin-bottom:-5px">
                    <rect width="100" height="20" style="fill:rgb(52, 101, 164)" rx="10"></rect>
                    <rect y="30" width="100" height="20" style="fill:rgb(52, 101, 164)" rx="10"></rect>
                    <rect y="60" width="100" height="20" style="fill:rgb(52, 101, 164)" rx="10"></rect>
                </svg>
            </div>

FOUR div doing maybe (and this is dubious) one DIV, one HEADER, and H!’s job. Static SVG in the markup missing a caching opportunity… and really if you’re loading your silly framework and still have to shart stuff on the BODY tag in the markup, you’re doing something wrong.

           <div id="nav" class="navigation" hx-boost="true">
            
                <div class="navigation-items" preload="mouseover">
                    <div><a href="/docs/">docs</a></div>
                    <div><a href="/reference/">reference</a></div>
                    <div><a href="/examples/">examples</a></div>
                    <div><a href="/talk/">talk</a></div>
                    <div><a href="/essays/">essays</a></div>
                    <div hx-disable>
                        <form action="https://google.com/search">
                            <input type="hidden" name="q" value="site:htmx.org">
                            <label>
                                <span style="display:none;">Search</span>
                                <input type="text" name="q" placeholder="🔍️" class="search-box">
                            </label>
                        </form>
                    </div>
                </div>
                <div class="github-stars">
                    <iframe style="margin:auto;" src="https://ghbtns.com/github-btn.html?user=bigskysoftware&repo=htmx&type=star&count=true" frameborder="0" scrolling="0" width="150" height="20" title="Star htmx on GitHub"></iframe>
                </div>
            </div>
        </div>
    </div>
</div>

Don’t we have a <nav> tag now? What makes those anchors slopped in there any-old-way a bunch of DIV? Isn’t a menu navigation supposed to be an unordered list? Where’s the <fieldset> for that form? (I’d probably break the search out into a modal)

And of course their broken mobile menu implementation is also scripting only… a laugh since I’d still have half the code using the hashlink:target trick.

It goes on like that for a while where apparently anything that’s not a H2 is a DIV. :/ But the real gems are stuff like:

<div style="border: 1px solid lightgrey; margin:24px;padding:12px;border-radius: 8px; background-color: whitesmoke; filter: drop-shadow(3px 3px darkgray)">
<b>NEWS:</b> We are excited to <a href="/posts/2023-06-06-htmx-github-accelerator/">announce</a> that htmx has been accepted into the first class of the <a href="https://accelerator.github.com/">GitHub Open Source Accelerator!</a>
</div>

screen only style in the markup because they were too lazy to make a “notice” class. Remember, 95% of the time you see style=”” and 100% of the time you see

A real gem though is this inside like they’ve got a nasty case of “turdpress stupid”

<style>
#sponsor-table td {
  text-align: center;
  padding: 16px;
  min-height: 100px;
}

@media only screen and (max-width: 760px)  {

 /* Force table to not be like tables anymore */
 table, thead, tbody, th, td, tr { 
  display: block; 
 }

}

</style>

Big tip, the STYLE tag is invalid inside no matter how many lazy, sleazy, inept devs ignore that rule. But that’s just the prelude to the REAL gem here:

<div style="overflow-x: auto">
<table id="sponsor-table">
<tr>
<td>
        <a href="https://www.jetbrains.com//"><img src="/img/jetbrains.png" style="max-width:30%;min-width:200px;"></a>
</td>
<td>
        <a href="https://github.blog/2023-04-12-github-accelerator-our-first-cohort-and-whats-next//"><img src="/img/Github_Logo.png" style="max-width:30%;min-width:200px;"></a>
</td>
<tr>

TABLES FOR LAYOUT!!! 1997 called, it wants its markup back.

The use of style=”” only further exacerbated by the complete lack of meaningful alt text. And yet the creator of this mess claims his script driven system is going to be accessible? They can’t even write accessible HTML!

That’s almost as stupid as using DIV with presentational classes on them like “row” or “col”. Not seen anyone do something that dumbass in a while…

       <div class="row" style="text-align: center;">
            <div class="col">
                <img src="/img/bss_bars.png" style="max-width: 30px; margin-top: 3em;">
            </div>
        </div>

Made worse again by the static style, and the fact there’s only ONE “cell” in it — a plate image.

Something else there is that we’re seeing a lot of PX, meaning that the layout is either broken crap , or they told large swaths of users to go F*** themeselves.

font-size:16px;

Ok, they told visitors to the site with accessibility needs to go f*** themselves; putting on full display they aren’t qualified to work with web technologies. Gotcha.

And of course, there’s no

The Script Itself

Is also riddled with broken, bloated, outmoded practices. To the point it proves something I said at the top. Their total lack of knowing JavaScript is why they’re diving to make alternatives, simultaneously making themi unqualfied to make the alternative.

Shades of myself a decade and some change ago to be honest. I mean I get it, sometimes by the time a project is ready to deploy it is obsolete, but…

I mean I can look the other way on the use of VAR, but a lot of modern scripting guys would take one look at that and balk. Same goes for the use of a IIFE. BUT to be fair I didn’t see the point of LET/CONST myself until I discovered I could replace IIFE with a simple {}.

But look at some of these gems… like recreating Array.forEach

        function forEach(arr, func) {
            if (arr) {
                for (var i = 0; i < arr.length; i++) {
                    func(arr[i]);
                }
            }
        }

When Array.forEach has been supported since IE10 / 2012… some partial browser support dating back to 2008. Much less why not just polyfill using Object.defineProperty?

       function toArray(arr) {
            var returnArr = [];
            if (arr) {
                for (var i = 0; i < arr.length; i++) {
                    returnArr.push(arr[i]);
                }
            }
            return returnArr
        }

So… never heard of Array.from, Object.entries, or Object.values?

        /**
         * @param {HTMLElement} elt
         * @returns {HTMLElement | null}
         */
        function parentElt(elt) {
            return elt.parentElement;
        }

        /**
         * @returns {Document}
         */
        function getDocument() {
            return document;
        }

I’ve seen functions for nothing before… but …

Reeks of the “functional programming” wankery where every single blasted little thing you do “needs” to be broken out into its own function.

The commenting style to make minification not strip the comments is a bit herpaderp too. You look at where some of that is used:

        function getClosestMatch(elt, condition) {
            while (elt && !condition(elt)) {
                elt = parentElt(elt);
            }

            return elt ? elt : null;
        }

And it’s logic junk. No check for if it’s even an Element, returning the element itself if there’s no match up the tree? No short circuit evaluation?

Laughably, it’s do-white to the rescue assuming they want it inclusive — aka as written it returns the element you pass if it meets the condition. I would have written that:

function getClosestMatch(elt, condition) {
  if (elt instanceof Element) do {
    if (condition(e)) return elt;
  } while elt = elt.parentNode;
  // default is null. 
}
  1. Amazing how many people don’t know the default is null.
  2. Amazing how many people lose their minds over efficient short-circuiting as “bad”
  3. Also stupid how many people would lose their minds over a do/while even when we know the condition is valid.
  4. I will say it was nice to see someone actually trying to work with the DOM.
        function getAttributeValueWithDisinheritance(initialElement, ancestor, attributeName){
            var attributeValue = getAttributeValue(ancestor, attributeName);
            var disinherit = getAttributeValue(ancestor, "hx-disinherit");
            if (initialElement !== ancestor && disinherit && (disinherit === "*" || disinherit.split(" ").indexOf(attributeName) >= 0)) {
                return "unset";
            } else {
                return attributeValue
            }
        }

Binary return condition (ternary’s job), else after return, shite formatting slopping everything onto one line…

Make no mistake, anyone doing “else” after “return” or encouraging doing so isn’t qualified to tell you a damned thing about programming.

But the real gems are stuff like:

       function makeFragment(resp) {
            var partialResponse = !aFullPageResponse(resp);
            if (htmx.config.useTemplateFragments && partialResponse) {
                var documentFragment = parseHTML("<body><template>" + resp + "</template></body>", 0);
                // @ts-ignore type mismatch between DocumentFragment and Element.
                // TODO: Are these close enough for htmx to use interchangeably?
                return documentFragment.querySelector('template').content;
            } else {
                var startTag = getStartTag(resp);
                switch (startTag) {
                    case "thead":
                    case "tbody":
                    case "tfoot":
                    case "colgroup":
                    case "caption":
                        return parseHTML("<table>" + resp + "</table>", 1);
                    case "col":
                        return parseHTML("<table><colgroup>" + resp + "</colgroup></table>", 2);
                    case "tr":
                        return parseHTML("<table><tbody>" + resp + "</tbody></table>", 2);
                    case "td":
                    case "th":
                        return parseHTML("<table><tbody><tr>" + resp + "</tr></tbody></table>", 3);
                    case "script":
                    case "style":
                        return parseHTML("<div>" + resp + "</div>", 1);
                    default:
                        return parseHTML(resp, 0);
                }
            }
        }

Tell me you know nothing about working with the DOM, Shadow DOM, or document fragments by telling entire flipping world you know nothing about working with the DOM, Shadow DOM, or document fragments.

For something that with some tweaking probably shouldn’t be much more than:

function makeFragment(resp) {
  const frag = new DocumentFragment();
  frag.innerHTML = resp;
  return frag.firstElementChild;
}

Though all the use of innerHTML and outerHTML being proof they’re not qualified to be doing any of this… from a performance, bloat, and security standpoint.

Good rule of thumb? If you see strings being declared in the JavaScript that looks like markup that has other strings being added into it? You’re looking at garbage JavaScript in all but the rarest of corner cases.

And the security risks is truly troubling. All this building markup in the scripting and slopping stuff in via innerHTML is just BEGGING for XSS exploits.

Conclusion

Like almost every other form of “Framework stupid” it’s painfully apparent they never understood enough HTML, CSS, or JavaScript to be creating an alternative method of doing things. The script itself is bloated, laden with redundancies, and a decade or more out of date on practices… To go with their markup practices and the very thing they are trying to do being little more than worshipping at the throne of the HTML 3.2 / 4 Tranny mentality.

Begging the question why — other than the pesky 3i of web development; ignorance, incompetence, and ineptitude — does anyone think this HTMX nonsense is worth using.

Again it just supports my assertion that the people who create frameworks do so because they refuse to learn, wallowing in willful ignorance. They hobbled themselves diving for alternatives before they even know if they need one, much less how to build one properly.

Recommended from ReadMedium