[HN Gopher] Locality of Behaviour (2020)
       ___________________________________________________________________
        
       Locality of Behaviour (2020)
        
       Author : jstanley
       Score  : 96 points
       Date   : 2025-07-03 14:17 UTC (8 hours ago)
        
 (HTM) web link (htmx.org)
 (TXT) w3m dump (htmx.org)
        
       | Swizec wrote:
       | I know the HTMX guy has _opinions_ on this, but I also like how
       | React (and similar frameworks) give you locality of behavior.
       | <button onClick={() => fetch('/clicked')}>
       | 
       | To me that feels like less magic to remember than
       | <button hx-get="/clicked">
       | 
       | But ultimately let's be honest: Experienced engineers can write
       | good code in anything and inexperienced engineers can write bad
       | code in anything. Simplicity takes time.
        
         | joshmanders wrote:
         | > To me that feels like less magic to remember than
         | 
         | Ehhh, only seems that way because you're so entrenched in React
         | with minimal htmx experience.
         | 
         | I've used both pretty extensively and they both feel similar in
         | the magic aspect, except htmx is cleaner.
        
           | alexvitkov wrote:
           | The react snippet is one uppercase letter away from being
           | vanilla js, it's definitely less magic.
        
             | yawaramin wrote:
             | I feel that as software engineers, instead of talking about
             | things like 'feels like magic', we are capable of reading
             | the docs and understanding what something actually does,
             | especially when it's pretty simple:
             | https://htmx.org/attributes/hx-get/
        
             | recursive wrote:
             | And if you made that change, despite being vanilla js, it
             | still wouldn't work.
        
           | pier25 wrote:
           | > _except htmx is cleaner_
           | 
           | I've had the opposite experience.
           | 
           | Htmx does work for simpler use cases like submitting a login
           | form. Beyond that it gets messy very quickly as you start
           | introducing more backend endpoints for every little
           | interaction. In some stacks you have template fragments[1]
           | which alleviate this problem somewhat but still, htmx doesn't
           | scale for more sophisticated interactivity.
           | 
           | And most projects will still need client-side interactivity.
           | So now your features are a mix of htmx stuff, something
           | client-side (Alpine, Vue, whatever), and probably some HTTP
           | endpoints to interact with the client-side stuff.
           | 
           | You also still need to take care of CSS which htmx completely
           | ignores because it's really just a low level HTML exchange
           | protocol if you will. With Vue, Astro, or Svelte you can
           | encapsulate markup, behavior, and styles in a single file.
           | 
           | And on top of all that, the DX is quite frankly terrible
           | compared to doing frontend with something like Vite with hot
           | module reloading. Most backend servers need to restart and
           | maybe even recompile the whole thing. PHP is the only
           | exception I know of since every request "runs the whole
           | application".
           | 
           | [1] https://htmx.org/essays/template-fragments/
        
         | mock-possum wrote:
         | Or lit -
         | 
         | <button @click=${fetch('/clicked')}>
        
           | qn9n wrote:
           | This is way too much magic, starting to look like Vue or
           | Svelte
        
             | zdragnar wrote:
             | That's because lit templates are in template strings, so
             | passing through a function requires the dollar sign and
             | curly braces (that's just vanilla js). Everything else
             | about it in the example, aside from using @click instead of
             | onclick, is identical to react.
        
         | PaulHoule wrote:
         | I see it as mattering at a higher level.
         | 
         | I worked on a system (Themis) for creating ML training sets for
         | big corporations which would basically show a screen that has
         | the user make a judgement and then show them another screen and
         | so forth.
         | 
         | We had to develop this thing at a breakneck pace and if it was
         | successful, I'd have the expectation that we (or the customer)
         | would always be developing new models and new tasks.
         | 
         | As it was this system had a React front end and a
         | "microservice" back end in Scala. Everything was packed up and
         | deployed with Docker. The engineering manager didn't believe me
         | when I said that it took 20 minutes to turn around the smallest
         | change in this system so we timed with it with a stopwatch and
         | we measured 18 minutes. Notably if you want to add a new task
         | you have to change more than one back end process _and_ update
         | the SPA. You had to be a skilled front end _and_ Scala
         | developer to change things yourself and if you 're working on a
         | team you are going to have a lot of back and forth which slows
         | things down.
         | 
         | I had a counterproposal (Nemesis) that I've been using for
         | Centaur processes [1] that I develop on my own account which is
         | an old-school site enhanced with HTMX.
         | 
         | A task is implemented in a Python file using the flask API with
         | a class that has a few flask methods for HTTP endpoints and a
         | small number of Jinja2 templates which draw the HTML. [2]
         | Adding a new task is a matter of adding a new class and a new
         | template and deploying it is super-easy, turnaround on a flask
         | server is instantaneous, it's just a few seconds with a
         | production gunicorn server. It takes less programming skill
         | than Themis, a new task can be created by cut-and-paste-and-
         | modify or be vibe coded without risk of breaking other tasks.
         | 
         | Since it doesn't have lots of images, chrome, Javascript,
         | metadata in the <head>, ads, trackers and crap it is crazy
         | fast. On my tablet, going the wrong way on an ADSL connection,
         | over wireless and my phone's hotspot, it feels like using a
         | desktop application. The app has plenty of screens that do
         | visualization with d3.js and other things with Javascript where
         | necessary. If I had a task that required a very complex
         | interaction then I could write an tiny SPA just for that task
         | with React, Svelte or whatever I wanted.
         | 
         | [1] Human processes some tasks, AI processes some tasks, AI
         | gets trained based on human performance
         | 
         | [2] The exact same architecture would work in Java with JAXB
        
         | yawaramin wrote:
         | But...your code examples are not doing the same thing though.
         | The React example just does a `GET /clicked` request and
         | _discards_ the response. The htmx example does a GET request
         | and _swaps_ the response into place, replacing the button. This
         | is analogous to an ` <a>` or `<form>` tag that makes a request,
         | gets the response, and then replaces (ie swaps) the page.
         | 
         | Try making your React example do what the htmx one does. That's
         | when you'll see the complexity start to creep in.
        
           | lblume wrote:
           | But how often do you actually need that when writing a
           | reactive app? In most cases I have worked with you want the
           | button to persist and load some external data that updates a
           | reactive state.
        
             | yawaramin wrote:
             | Sure, try doing whatever equivalent makes sense in React.
             | Then try comparing the complexity of the two approaches.
             | The point is to compare apples to apples as much as
             | possible.
        
         | tshaddox wrote:
         | > Experienced engineers can write good code in anything and
         | inexperienced engineers can write bad code in anything.
         | 
         | I don't have strong opinions about this particular React/HTMX
         | example, but I don't love this statement because it seems to be
         | dismissing the possibility that some tools are better than
         | other tools for a particular job.
        
           | Swizec wrote:
           | > it seems to be dismissing the possibility that some tools
           | are better than other tools for a particular job
           | 
           | I think these are 2 dimensions. Great engineer + right tool
           | is obviously the best combination. Great engineer + wrong
           | tool can work surprisingly well. I have never seen the other
           | 2 combinations produce elegant results.
           | 
           | But really what I was trying to say is this: We're just
           | smearing complexity around. UI is hard. The component model
           | won for a reason. Use whatever syntax you prefer, they're all
           | fine, but _please_ use composable components of some sort to
           | build your UI.
        
             | yawaramin wrote:
             | Htmx doesn't preclude a component model for the UI. See:
             | 
             | - https://www.fastht.ml/
             | 
             | - https://github.com/yawaramin/dream-html/tree/todoapp/app
             | 
             | In fact if you look at how htmx is recommended to be used,
             | you realize they specifically are recommending a component-
             | based architecture: https://htmx.org/essays/when-to-use-
             | hypermedia/#if-your-ui-i...
        
               | Swizec wrote:
               | That is the point I was making: HTMX is just a flavor of
               | the component model.
        
               | yawaramin wrote:
               | Eh, not really. Htmx is a tool that you can plug in to
               | your system. It doesn't dictate what system or
               | architecture you use. You can have a component model, or
               | you can have old-fashioned string-based HTML templates.
               | Htmx is agnostic to that.
        
           | marcosdumay wrote:
           | It is exactly dismissing the possibilities some tools are
           | better than others for particular jobs, and it has been a
           | popular phrase for people advocating for bad tools for
           | decades.
           | 
           | The previous favorite one used to be "a bad craftsman blames
           | his tools", that people that liked to impose bad tools on
           | others repeated all the time. But that phrase got old.
        
         | perching_aix wrote:
         | > let's be honest: Experienced engineers can write good code in
         | anything and inexperienced engineers can write bad code in
         | anything
         | 
         | Does this not scream "just the distribution of outcomes can
         | drastically differ" at you?
        
           | Swizec wrote:
           | > Does this not scream "just the distribution of outcomes can
           | drastically differ" at you?
           | 
           | Yes. My argument is that outcomes depend more on the people
           | than tools.
        
             | perching_aix wrote:
             | How does that follow? Or are we talking past each other?
             | 
             | I tried to model this formally (well, to the extent I can
             | model anything formally), cause this is a fun little
             | exercise in a way, and got that at least as far as my toy
             | model goes, it's a wash, both matter equally (since at the
             | extremes, they will approach each other's distributions,
             | i.e. the distribution of expertise or the distribution of
             | tool choice).
             | 
             | Or to be more specific, what matters more is what you can
             | change more, something I only accounted for in the very
             | end. And I don't think significantly pushing the needle in
             | expertise, especially in a way that persists over time, is
             | realistic. People need time to gain expertise, so those
             | with majority expertise will always be a minority. But
             | improving tooling and doing so with long lasting effects is
             | _much_ more realistic. Hence, good tooling that leads your
             | hand matters more. It raises the bar, and automatically
             | ensures past mistakes aren 't repeated without a need for
             | costly rediscovery or specific training.
        
               | Swizec wrote:
               | I think we are violently agreeing.
               | 
               | Think of frameworks as "distilled experience". They help
               | the next "generation" go faster because they don't need
               | to make all the same mistakes. This is awesome.
               | 
               | But it is not magic. People _will_ make a mess. Often
               | because they hold the framework wrong and /or miss what
               | it's trying to do for them. Or just don't grok the
               | author's beautiful vision.
               | 
               | However, an experienced practician groks the fundamentals
               | and can quickly adapt to any framework. Because all the
               | frameworks are built on similar fundamentals so it's easy
               | to hold them correctly. You go _" Ah, the author believes
               | X, so if we just assume X, it all falls into place and
               | the code looks great"_.
               | 
               | That said, you might be working in a domain where X is
               | fundamentally untrue. Then this is the wrong framework
               | and an experienced practician will go _" This is the
               | wrong tool and choose a tool that fits better"_. The
               | thing that doesn't fit may be the current team/experience
               | moreso than the business domain.
               | 
               | At the end of the day more experienced engineers will
               | produce a better solution than less experienced
               | engineers. For a variety of reasons. Good tools can make
               | less experienced engineers ramp up faster.
        
               | perching_aix wrote:
               | Oh okay, we are indeed in agreement then.
               | 
               | > That said, you might be working in a domain where X is
               | fundamentally untrue. Then this is the wrong framework
               | and an experienced practician will go "This is the wrong
               | tool and choose a tool that fits better".
               | 
               | Agreed, this is always the money shot. I further think
               | that the holy grail for advancements is for them to lead
               | people's hands in a way that is self-justifying. It's a
               | very tall and cruel order, but I think it's absolutely
               | essential. If a solution fails to do this, people
               | eventually turn on it, and adoption reverts or otherwise
               | falls apart. I'm particularly worried about this
               | happening to e.g. Rust, but this can be applied to
               | basically anything.
        
       | shelajev wrote:
       | "spooky action at a distance" sounds bad, but this is how most
       | frameworks that embrace convention over configuration work. You
       | add a dependency to your SpringBoot application and suddenly your
       | app actually has new endpoints and config for them and so on.
        
         | Pannoniae wrote:
         | Yes, but that doesn't necessarily mean it's the right thing to
         | do, it's a tradeoff. Convention over configuration is good in
         | large teams for fast onboarding sure (just copy the previous
         | pattern!) but it makes the last 20% of stuff harder and it
         | especially makes debugging harder because all your IDE tools
         | like "find usages" become useless.
        
       | qoez wrote:
       | I remember learning separation of concerns and then react popped
       | in and just threw that out the window and yet everyone adopted
       | it. I still haven't recovered from the idea that these guidelines
       | are very bendable etc.
        
         | dsego wrote:
         | There is separations of concerns and separation of
         | technologies.
         | 
         | A component with its styling, markup structure, and behavior,
         | is one concern. These components can than be combined and
         | nested in every which way you need, they are very modular.
         | 
         | If you separate concerns into markup, styling, behavior, than
         | this form of component-based reuse is difficult. But it could
         | conceivably be easier to reuse an html template or css class
         | for different features.
         | 
         | Hooks are also a way to separate concerns in a different way
         | than lifecycle methods. Lifecycle methods group code by
         | specific points in a component's life, but then the logic can't
         | be extracted and reused. Hooks on the other hand encapsulate
         | reusable logic that can then be used in different components.
        
           | kqr wrote:
           | Thank you for saying this so clearly. I find React to be one
           | of the few systems that actually allow separation of concerns
           | when used idiomatically, rather than drawing boundaries based
           | on technologies. (And I dislike frontend programming, to be
           | clear!)
           | 
           | https://entropicthoughts.com/mvc-mistake
        
         | panphora wrote:
         | It's all bendable, always has been.
        
         | mystifyingpoi wrote:
         | > and just threw that out the window
         | 
         | Did it though? I've worked with React for some time, and we
         | simply put all the non-view things into separate modules, or at
         | least separate functions. React is (still) a library, it
         | doesn't prevent anyone from mixing the layers and making
         | spaghetti.
        
         | tshaddox wrote:
         | Was it CSS Zen Garden that convinced a generation of web
         | developers that HTML and CSS are in fact different concerns?
        
           | blturner wrote:
           | CSS was brand new. It was a showcase to show a generation of
           | web developers who used HTML tables for layout (probably
           | using Dreamweaver) how CSS and semantic HTML could be used to
           | achieve the same result, but with readable code. In some
           | ways, component-based architectures are just a re-hashing of
           | the old paradigms we worked out of.
        
           | gf000 wrote:
           | So where is the booming community of CSS styles for popular
           | websites, so that I can just change the way they look how I
           | wish? Surely making one for Facebook would be no issue,
           | right?
        
       | incorrecthorse wrote:
       | The bad ID naming `d1` is convenient for the argument...
        
         | mock-possum wrote:
         | So is the assumption that the jquery is in a different file
         | than the html tag - if you wanted to get real crazy about it
         | you could have the script tag immediately after the button tag
         | - you could even inline the onclick behavior with no jquery or
         | htmx required. How's _that_ for locality?
         | 
         | I guess the point is not so much that you could do those
         | things, but that it was fairly common during 'the jQuery days'
         | to do as the author describes.
        
       | alganet wrote:
       | Doing separated CSS/JS does not imply in automatic breaking of
       | LoB.
       | 
       | You just need to come up with CSS and JS that are not _specific_,
       | but _layered_ instead, which is hard.
       | 
       | Bad:                   .one-specific-button-from-the-nav-bar {
       | color: blue; }         <input class="one-specific-button-from-
       | the-nav-bar"/>
       | 
       | Good (only this button):                   <input style="color:
       | blue"/>
       | 
       | Better (all buttons on nav are the same):                   nav
       | form button { color: blue; }
       | 
       | What if I want two kinds of buttons on the navigation bar?
       | 
       | Then you have two options:
       | 
       | - Abandon the idea. Do something simpler.
       | 
       | - Come up with a generic CSS rule for it.
       | 
       | For this idea to work, _you have to accept limitations_, which
       | means you cannot design whatever looks you want, or make pixel-
       | perfect layouts, etc. You have to work within some constraints.
        
         | recursivedoubts wrote:
         | yeah, this is a tricky area for us LoB enthusiasts: tailwinds
         | is an example of LoB, _but also_ I think sometimes it devolves
         | into inlining implementation[1]. My gut reaction is to want a
         | well named CSS class on the element instead, which feels like
         | the equivalend of defining a function elswhere, but calling it
         | on the element in question, thereby preserving LoB (to an
         | extent)
         | 
         | On the other hand, I know a lot of very smart folks who like
         | the tailwinds approach, and I do acknowledge the difficultly of
         | naming CSS classes, that the function metaphor breaks down due
         | to the fact that CSS classes have non-encapsulated effects on
         | child elements, etc.
         | 
         | Interesting case to think about.
         | 
         | [1] - https://htmx.org/essays/locality-of-behaviour/#surfacing-
         | beh...
        
           | alganet wrote:
           | tailwind bad
        
         | pphysch wrote:
         | There's a middle ground which is having an optional <style>
         | block in the page template, this allows you to express local
         | style differences without going crazy with style attributes.
         | And they are easy to refactor into a global style sheet if
         | needed.
        
           | alganet wrote:
           | I think the web platform is really simple, easy to understand
           | and great.
           | 
           | A. Your content is a document. A semantic HTML document,
           | boring, mostly made of text.
           | 
           | B. You can make the document alter parts of itself with JS.
           | You don't mess with appearance here, just change the boring
           | document content.
           | 
           | C. You can make the document look good by applying some
           | styles to semantic HTML tags.
           | 
           | If you respect them, you get both LoB and SoC neatly arranged
           | into meaningful layers.
           | 
           | --
           | 
           | Tailwind-like stuff breaks semantic HTML. Classes like
           | ".flex-7" are about style, not the text. CSS classes should
           | be stuff like ".introduction" or ".cooking-ingredients".
           | 
           | It makes documents otherwise simple to read into a mess. Now
           | there's appearance concerns hidden in between the content.
           | It's bad.
           | 
           | --
           | 
           | All good rules come with great exceptions. In this case,
           | stuff like Google Maps. You can't really represent it as a
           | document. But that's rare on the web.
           | 
           | Somehow, people were convinced to use those "frameworks for
           | rare use cases" to make simple documents, forms, etc.
           | 
           | --
           | 
           | Local <style> tags fall into this category of exceptions.
           | Sometimes you need some extra style for a content that is
           | unusual, but that's the exception. Most websites want to look
           | consistent across all their published content.
        
       | khy wrote:
       | It's kind of an expression of the success of an abstraction. If
       | an abstraction is good (hx-get="/clicked"), it feels local. If an
       | abstraction is bad (id="d1"), it's spooky action at a distance.
        
         | dalmo3 wrote:
         | I've never used htmx, can you ctrl+click "/clicked" and go to
         | definition? If not, it's just as spooky.
        
           | pphysch wrote:
           | HTMX can easily get spooky. One pattern to avoid that is to
           | not use separate routes for HTMX endpoints. Instead, use a
           | special GET param or header to tell the server that this
           | request is for a specific HTML fragment.
           | 
           | /page1, /page1/click1 (bad)
           | 
           | /page1, /page1?htmx=click1 (good)
           | 
           | Then have a common server pattern for how you inspect the
           | request to determine which fragment (or whole page) to
           | respond with.
           | 
           | With this, navigating HTMX code is much nicer because you
           | only have to identify one entry point instead of hopping
           | through the codebase to identify the N different URLs and
           | view functions that support the page.
        
             | recursivedoubts wrote:
             | For sure, for example, htmx has attribute inheritance,
             | which moves behavior away from where it has effects, and
             | allows you to listen for events on other elements, same
             | problem.
             | 
             | The development principle still stands, however, and, like
             | all dev principals, has trade offs associated with it.
        
           | fkyoureadthedoc wrote:
           | can you do that on any button that makes a fetch/ajax call?
           | if the target for /clicked is a full standalone page, why
           | even have a button and not a link?
        
           | recursivedoubts wrote:
           | Have you ever used HTML?
           | 
           | Can you ctrl-click on an href attribute and go to the
           | definition?
        
           | yawaramin wrote:
           | > can you ctrl+click "/clicked" and go to definition?
           | 
           | Actually, you can and you should-this is a best practice for
           | building maintainable apps. You shouldn't be hard-coding
           | 'magic string' API paths throughout your views. You should
           | factor them out into variables and then use those same
           | variables for both routes _and_ views.
           | 
           | Many good routing systems have this functionality.
        
       | hobozilla wrote:
       | Sorry in advance for bringing AI into this but... this is context
       | engineering in a nutshell. DRY and SoC really mess with an
       | agent's ability to efficiently gather context. If you limit all
       | layers of a feature into a small area (e.g. folder) it's much
       | easier to work with. Vertical Slice Architecture is a good
       | example of this too.
        
         | ryeats wrote:
         | It's a good point though this is also what makes it easier for
         | humans. I think most good abstractions hide unimportant details
         | in order to make it easier to reason about and reduce the
         | context but both the appropriate amount of context and what is
         | an unimportant detail are not consistent between individual
         | devs and don't have good metrics to measure their qualities.
        
       | ribs wrote:
       | One would like to see the word "modularity" somewhere here.
        
       | pier25 wrote:
       | Styles are also part of the behavior of a piece of markup. Not
       | the interactivity behavior but certainly the rendering behavior.
        
       | jonathan-adly wrote:
       | One of the most pleasant experiences I had writing code, is early
       | AI days when we did hyperscript SSE. Super locality of behavior,
       | super interesting way of writing Server Sent Events code.
       | 
       | eventsource demo from http://server/demo                   on
       | message as string             put it into #div         end
       | on open             log "connection opened."         end
       | on close             log "connection closed."         end
       | on error             log "handle error here..."         end
       | 
       | end https://hyperscript.org/features/event-source/
        
       | BeetleB wrote:
       | Yet another article that misrepresents DRY.
       | 
       | (DRY was never about code repetition).
        
       ___________________________________________________________________
       (page generated 2025-07-03 23:01 UTC)