Hacker News new | past | comments | ask | show | jobs | submit login
Behavior belongs in the HTML (unplannedobsolescence.com)
93 points by alexpetros 4 months ago | hide | past | favorite | 54 comments

This seems a bit discombobulated. You shouldn't be using a `<button>` at all but a `<input type=submit ..` for most of the examples. The search example shouldn't be changing it's name (query->q) for new practitioners following along, and should be using `<input type=search`.

In the `too-short-message` example where is the `not-long-enough` message defined?

Then we run off into microformats, they're great, but out of scope (and never tied back).

The button extension using custom attributes should be getting attribute `message` (not `type`).. but that's probably a typo. A potentially more reasonable attribute would be `title` since in many desktop browsers you'll get a hover tooltip (which might be beneficial, although for the contrived example perhaps not.. on the other hand, why would you want a multitude of alert buttons?)

And then there's zero consideration for Content-Security-Policy[0], one of the current reasons you'd avoid inline JS (`unsafe-inline`) in favour of script tags that can have `nonce-*` or `sha256-*` signed content.

[0]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

<input type="submit" value="Search"> is fine and is the most common solution. But <button>Search</button> is also completely fine, and if you want anything other than plain text as a button label (e.g. images, variable formatting), it’s the approach you have to use. (When used in a form, there’s an implied type="submit" on it. With type="button" it would lose the submitting functionality and become obviously unsuitable.)

Fixed the "message" and "query" typos, thank you! I iterated on those examples a bit and didn't get them all back into line.

As for CSP, my point isn't really that you should be throwing `onclick`s on everything, it's more that you should think of HTML as the place to define most of the page's behavior (where appropriate) using elements and attributes. A script tag could implement that interface too.

The best way to deal with CSP is to never allow a CSP directive in your HTML or response headers. It's one of the worst, most confused, harmful specifications in the collection of modern Web standards. At the very least, anyone who isn't on the security team at an org should be forbidden from touching anything related to CSP. (If the org isn't big or organized enough to have a security team, that would be a set of size 0. Seriously, don't use it.)

`<button type=submit>` makes more sense to me than `<input type=submit>`

It’s really amazing watching trends in web UX reverse in realtime. Can’t wait to log in and find one day that flexbox/grid are bad and we’ve reinvented table and frame layouts.

There are loads of web apps that have rich interactivity where mixing presentation with supporting logic creates a real mess. Say you’ve got a grid with a million (gasp!) rows. You’re really gonna emit all that unnecessary inline JS behavior over the wire?! Why on Earth?? What if you need context binding? You could just have a jQuery .on handler registered once at the parent and filtered to all buttons, which pulls the id of the record from a data-attribute on the target element, finishing the same work in a couple of lines of code in one easy to maintain spot. Yes, adding addEventListener code all over your app would be repetitive and wasteful, but that (amongst browser incompatibilities) is why jQuery was invented, but you can hand roll your own utility or use HTMX or a million concise tools to do this. Including a right-sized dependency isn’t going to kill you. Sure, if you’ve got a stupidly simple HTML page with a couple elements with a handler or two, fine, go ahead, knock yourself out. I disagree with this one-size-fits-all assertion. We did a lot of this before and it sucked.

> Yes, adding addEventListener code all over your app would be repetitive and wasteful, but that (amongst browser incompatibilities) is why jQuery was invented, but you can hand roll your own utility or use HTMX or a million concise tools to do this. Including a right-sized dependency isn’t going to kill you.

This article is explicitly a defense of the interface that htmx (which I am a core maintainer for) employs. I think more JS libraries should use attributes as their interface. As for when and where that's appropriate, well, like anything, it depends on the context, but I'm confident in saying it's appropriate in more contexts than it is currently being used.

Just to add, what irks me is that recent culture of arrogance that enables less experienced devs to assume that all the old ways are terrible and that this new way of doing things is the only way there will ever be.

What ever happened to modesty in juniors or apprentices?

Maybe too many of us just age out and leave the industry :-/

The way Tailwind is setup, we may as well bring back the <font> tag.

I did not see mention of event delegation in the article.

Registering thousands of event handlers on one screen can have performance impact, and is generally why registering them on individual elements becomes a problem for an interactive page. Registering on a parent element can provide the same interactivity with only a single handler thanks to event bubbling.


This is a good point. I think it's sort of an implementation detail, but it also implicates how you might design the interface (i.e. with inheritance or not). In any case, thanks for the link.

There is definitely something to be said for the locality of the definition with the element. Tailwind has shown that many (myself included) see defining your styles directly in your elements as preferable to doing it externally.

JSX and templating in other frameworks gives you that desirable locality, which I think is why it can feel so painful to go back to vanilla js.

I still default to plain ol' vanilla JS on a lot of things, but having to add definitions to elements after first grabbing them with selectors is annoying, particular for singular elements.

Tailwind has shown that, absent personnel with expertise and time to focus on a design system separating relevant concerns and/or absent tooling that can give you read/write changes, inline declaration of a somewhat constrained set of predefined options solves certain specific problems.

A lot of the related complaints -- including some in this article -- basically boil down to the old complaint about object oriented programming: "everything happens somewhere else" so what's actually going on isn't something you can read in one place, you either have to wind your way through the various scope/inheritance contexts to build up a picture of the code/rules in play, or you need to have tooling do it for you.

Or... you put it inline, mixing concerns, and eventually discover the tradeoff that comes with that choice.

The thing about OOP is that you can always trace down the methods as they’re called, or look at the parent class. JS can come from any place, reminding me of PLEASE COME FROM in INTERCAL. It’s not that, the fundamental issue is that the DOM is a big global that everything has access to.

But you can’t look at a piece of HTML and know for sure it doesn’t have an event bound to it without reading every scrap of JS (or just running the code). In OOP at least you (should) have some guarantees of encapsulation. But since I don’t see an obvious way to make HTML more OO, I can see the appeal of a declarative style.

I'm using Web Components for this stuff, and it works. If I need variant behaviour, I define a new tag and spec out the behaviour in JS. This removes the need for a vanilla JS routine that grabs the element with selectors and twiddles it. This feels like an improvement, even for a single tag :)

But I'm not doing anything massively complicated yet, so I don't know how it scales for complex UI interactions.

For my learning, what kind of state management do you use or see commonly used with web components?

I currently have three large components that handle state between them, and a bunch of control components that don't care about state (they receive values as attributes, and trigger "change" events if changed by the user).

The larger components contain their own state, and communicate by events (so component Foo emits a change event when its state changes, and component Bar listens for that event and fetches the new state by checking event.target.data).

It's working reasonably well with this relatively small set of interactions. But I can see a point where I'll have to create a Flux/Vuex-style state store and use actions to mutate it.

I use something similar to React class components. Under the hood my set function calls Object.assign and I debounce the render function callback. You could implement it yourself in <80 lines of JavaScript[0]. I'm not sure what other people use but I've been using this style of state management for ~4 years and it's been fine so far.

[0]: https://github.com/codewithkyle/supercomponent/blob/master/s...

Edit: if signals are your jam you could us https://github.com/maverick-js/signals

My suspicion is that if you have a single page where there are lots of interdependent elements that update state together, then it may become difficult to maintain.

I agree. But the strategies that Elm/React/Vue/etc use to deal with this can be copied without taking on all the rest of the baggage. Creating a Flux/Vuex-style state container and a bunch of mutator functions isn't hard.

This is an enjoyable read, and while many of the specifics are arguable, the core essence, to me, is encapsulated by a famous Ralph Waldo Emerson quote: “A foolish consistency is the hobgoblin of little minds, adored by little statesmen and philosophers and divines."

Too much of this industry is dogmatic pursuit of absolutes. The submission notes the documentation declaring that one should never use event attributes, arguing an illusory separation. Similar sorts of absolutism can be found in almost every tactic and approach. Too often many seek to simplify the choices we need to constantly make by making some of the options verboten. Its why everything old is new again and we seem to constantly have camps reverting to forbidden approaches and now decrying their replacements as the bad option.

I probably should have put this caveat in the conclusion but a couple people made some version of this point so I'll put it here instead.

I don't think behavior belongs exclusively in the HTML. There are types of webapps and complex UIs that require some form of involved state management and HTML—or, more generally, Hypertext as the Engine of Application State—is not the right solution for that. In that sense I want this post to be against the dogma of "behavior exclusively belongs in JavaScript," because I think that's incorrect and it's a pretty common view, even from expert sources that I respect.

I am, however, somewhat dogmatic in believing that web pages whose behavior can be described declaratively, in the HTML, using built-in attributes or a carefully selected superset of additional attributes, absolutely should be. This is because declarative interfaces almost always last longer and require less maintenance (I plan to write about and explore this topic in a lot more detail). A great many web pages that are not written this way could be, and suffer from an increased maintenance burden as a result.

I personally like the separation of concerns that HTML/CSS/JS evangelizes and I feel like it lines up well with how the old school MVC frameworks want you to think about things.

I think a good middle ground here is that HTML web components article that was shared here a few weeks ago: https://gomakethings.com/html-web-components/

I like this because it's still fairly low-code but you get cool stuff out of the box like attributeChangedCallback. You don't have to mess with build tools, which is what I like the most about this strategy. I can just drop these components into my Rails views like any other element, and their custom functionality gets mounted up as soon as they get folded into the DOM. No more document.querySelectorAll(".foo-bar").forEach(foo => bar()). Much cleaner, and the intent is more clear too. With <foo-bar>, it's much more obvious that the element has scripting attached to it than <div class=”foo-bar">.

Thanks for sharing this article, I really liked it!

This doesn't really resonate with me. It feels like the author's main point boils down to the fact that the only currently (officially) supported way to have custom attributes in HTML is with data-* attributes. I guess that's a fine opinion to have, but their example in the conclusion really doesn't look very different to me than if the same code was written with `data-alert data-message="..."`. One big reason I like using javascript for behavior is that it's an actual programming language, so I get all the benefits of things like modules and variables. Its reasonable to argue that more custom attributes should be used vs. lots of verbose JS, but that's what data attributes are, I don't see how 5 characters is hurting us that badly. The author criticizes data attributes for being implicitly focused on data rather than behavior, but I'd argue the author's own final example is also defining data rather than behavior: adding an "alert" attribute is a piece of data that indicates the button will show an alert on click (a behavior that the author adds with javascript) and the "message" attribute indicates the message that will be shown (inherently a piece of data IMO). I must have missed something because I found this article entirely unconvincing.

I still find that HTMX et al. have weird tooling. I get so much value out of a debugger, especially with event handling, and getting a weird mystery Alpine/HTMX/Hotwire call stack back is kind of weird.

Still though, I like it in theory. JSX still feels closer to an “ideal” syntax to me, even if there’s a lot to complain about there as well.

No question this is true. I do think that the relative immaturity of the these frameworks (from a secondary tools standpoint) is balanced out by the fact that the in-browser debugger is much closer to the actual code you wrote, something I wrote about previously[0].

Definitely more work to be done in this area though, and I tried to keep this post mostly theoretical for that reason.

[0]: https://htmx.org/essays/no-build-step/#developer-experience

Oh heya man! I’ll give it a closer read when I’m not wasting time at work on HN haha

I will say TS source mapping gives me a near identical experience to debugging JS. Even if it’s another artefact to cart around, it’s near invisible once set up. It feels close enough to the actual code I write.

While a debugger looking at an HTMX app does show exactly what is running at that moment, it peels back the mental model HTMX sets up to reveal the inner workings. That I feel like could be more confusing for folks learning.

Thanks for making cool stuff though! :) (genuine! The internet can be a snarky place)

If the choice is between using HTML in JS (e.g. JSX) vs using JS in HTML (essentially, that's what HTMX is), my choice is unequivocally the former. Ignoring some very basic use cases, UI apps are all about state and interactivity. HTMX can somewhat handle interactivity (very awkwardly in my opinion), it offers nothing over React in terms of state management.

In terms of state management, it's a bit of an apples-to-oranges comparison, no? It offers nothing over React because it doesn't offer any state management. AFAIK HTMX is a wrapper for the fetch function that lets you define your request from your HTML. How you manage the state on the server is up to you.

That's where I disagree with this approach. IMO sending over fully rendered views just because some state changed is antithetical to the UI needs. UI is the state that users interact with.

Apparently we're going through a stage right now when the loud voices have decided everything belongs in the HTML. Tailwind and HTMX are both frameworks trying to stuff everything in the HTML, which works great in simple demos and becomes hilarious in any more complicated real-world project.

The naive and inexperienced will follow those trends, and will generate countless "why I stopped using..." and "... considered harmful" blog posts 1-2 years from now, when frustrated, they'll flip on their God and declare them Satan.

Those who have been through these cycles before will smile, shake their head and know there's nothing that can be said to dissuade them, until they learn painfully from their own experience. And the wheel keeps turning.

For the record, behavior doesn't "belong" anywhere. The optimal solution varies based on the type of project and content. Considering the historical baggage of HTML and how specific it is to the browser, while many apps are multiplatform these days, I could easily argue HTML is the worst place to put your behavior.

The entire second half of the article acts as if custom elements aren't already supported in HTML:


You can go ahead and create <whatever> <custom-elements> with whatever attributes you want and it'll work.

Custom elements are supported, but custom attributes on existing elements are not—an asymmetry that the WHATWG proposal I linked to makes note of.

This is a helpful and concise explanation. As I read the article, I continually wondered when custom elements would appear, and was surprised that they didn't. Adding this context around the link you mention would help.

(I didn't click on the link because I was focused on absorbing your article in one go; now that I've looked at the link, I think that your sentence is much more concise for the reader.)

context: the author is a core maintainer for HTMX

The author's argument about SoC is not convincing. He: "is, in my polite opinion, completely wrong". The graybeards are right on this one.

For those of us who remember working with "DHTML" before libraries like jQuery proliferated, and then jQuery became ubiquitous for a few years - many of us recall the messes that the simplistic technique of using intrinsic events gave us.

When intrinsic events were introduced (e.g. the onclick attribute), they were meant to wire up some simple DOM behavior to Java Applets. Then somebody convinced the W3C people that it was a neat idea, and it needed to be in the HTML 4 spec.

I die inside a little every time I read a sweeping assertion followed by some contrived examples. Does anyone find this kind of thing convincing? You can make anything sound plausible if you pick the right cute little snippets.

I find it interstring the author goes from saying "This is, in my polite opinion, completely wrong." with regard to this from MDN: "You should never use the HTML event handler attributes — those are outdated, and using them is bad practice." to finally presenting a solution that doesn't use HTML event handler attributes. I was expecting them to show me the use of these attributes in their solution, but they don't.

I'm more of a fan of this than the separation; I'd rather have a generic library provide the data-* options for things and keep the semantics more local.

Focusing in on the contrived example re: native <form> behavior and nothing else, if given the choice I would rather remove the default behavior / "action" than adding more functionality to html.

I love html, and I love writing nice clean markup that clearly defines a document, so that you can look at the html without css and js and know what information it is presenting to you. I really don't want it to do anything else.

I think the author is confused here. HTML belongs to the internet of the library and has been feature complete for some time now.

For the internet of the shopping mall you would use HTMX or some such and that should be enhanced or pimped to the max by all means. Inline, webassembled and reactified riding the tailwind. Go for it, knock yourselves out and make a killing. How about bigger screens for all the popups?

need more buttons

I don't want to disagree because you put a lot of thoughts in it obviously, but HTML also stands for? :o)

So although you're right that it does a bit more, it wasn't and probably shouldn't be used for other than document presentation over the web.

That's the reason javascript got introduced: the custom designed and dynamic parts.

This article seems well meaning, but it contains factually incorrect information.

Its is also redescribing well defined concepts subjectively but presenting them objectively, and this is unfortunately a constraint that eliminates the value of the article.


It sounds like this author wanted to recommend web-components but doesn't know they exist. This is exactly what web-comps solve, writing custom HTML elements that are interacted with like native ones, in native HTML.

I'm learning to use htmx these days, I just wish htmx can have a little more local interactivity builtin instead of using hyperscript, which is hard to memorize its syntax.

I do love how deeply & carefully Alex thinks about this stuff.

Zenarmor WAF classified this site as a "dead site" and blocked it.

Just wanted to let you know OP.

I bought this domain like a year or two ago, maybe someone had it before? If there's something I can or should do about that please let me know.

Sounds like... impromptu deprecation.

Time is a flat circle.

People seem to forget that HTML is a markup language used to describe the content of a document. Once one remembers that, and thinks about that, everything about HTML becomes very clear, easy and manageable.

Apple sends their regards. Google would like a word.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact