[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)