使用 Htmx 构建渐进增强的表单
Building progressively enhanced forms using Htmx

原始链接: https://www.rafa.ee/articles/progressive-enhanced-forms-htmx/

本文探讨了如何利用渐进增强(progressive enhancement)构建交互式的服务器渲染表单。其核心思路是先基于标准的 HTML 特性,再叠加 HTMX 以改善用户体验。 非单页应用(SPA)开发中的核心挑战在于管理“瞬时状态”,即那些尚未存入数据库,但在服务器往返过程中必须保留的用户输入。作者强调了以下几种策略: * **表单值(Form Values):** 最适合简单输入,但容易受页面刷新及多个表单冲突的影响。 * **查询参数与路径(Query Parameters and Paths):** 适合需要在页面刷新后保持不变,或需要通过链接分享的状态。 * **表单拆分(Form Splitting):** 一种能够提升键盘可访问性并隔离表单操作的实用技巧。 作者提倡“无 JS 优先”的原则以确保功能的稳健性,仅在需要优化(如实时搜索或加载提示)时才引入 HTMX。在管理界面更新时,作者更倾向于替换较大的区域,甚至整个页面,以避免因过度细粒度的更新而导致的复杂性和数据陈旧风险。通过利用浏览器原生的行为,开发者能够构建出可靠且高效的界面,而无需依赖现代 JavaScript 框架中常见的繁重依赖链。

Hacker News 最新 | 往期 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 使用 Htmx 构建渐进式增强表单 (rafa.ee) 11 分,由 mpweiher 发布于 2 小时前 | 隐藏 | 往期 | 收藏 | 1 条评论 | 帮助 arthurcolle 11 分钟前 [–] 视频似乎无法播放,开始和结束时间都显示为 0:00 回复 考虑申请 YC 2026 年秋季班!申请截止日期为 7 月 27 日。 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文
; Free of LLM-generated text

I recently built a new form for ties, with lots of interactivity. It’s for editing a bookmark and connecting it to lists:

A screencast of a form for editing a bookmark. The user presses a button to rename the bookmark to the title fetched from the bookmarked website, then searches for lists to add the bookmark to.

The feature uses progressive enhancement: it works without JS, but uses HTMX to add some niceties when JS is enabled.

In this post, I’ll go through my approach and talk about a few things to keep in mind when building a UI in this style.

Transient State

Interfaces that work without JS are fundamentally different from single page applications: updating the UI in response to user interactions will often require a roundtrip to the server (usually initiated by a or form elements).

Before the user completed their task and you’ve saved something to the database, you’ll have “transient state”: Input that the user entered, that isn’t persisted (yet), and that needs to survive this roundtrip.

One example from the form described above would be a new name for the bookmark. When the user hits the “rename” button, the name is sent to the server, and if there are validation errors, the server will return a response showing an error. The response includes the users input value, so they don’t have to type it again.

In an SPA, transient state would live in JS memory, and you’d use some library with an unholy amount of dependencies to manage it. Since we’re making do without JS, we’ll have to use classic HTML features to manage this state, each with its own set of tradeoffs.

Thinking about how this transient state flows through the system will help mapping complex interactions to HTML.

Form Values

This is used for the “rename” field from the example, and the most commonly used one. When the form is submitted, the server receives these values and can include them in its response so they don’t vanish when the user interacts with the server.

It’s simple, and most likely the technique you’re already using for lots of user input.

A drawback of this technique is that values will disappear if the user reloads the page. Another one is that values are only submitted for one form - if you have multiple forms on a single page, values from the other forms will vanish on submit. For example, the bookmark edit form above is split into two forms: one for renaming the bookmark, and another one assigning lists (I’ll go into the reasons for the split later on). You can work around this using HTMX, so users without JS will experience graceful degradation.

Query Parameters and Paths

By putting state into the path or query parameters of the page, it will survive across page reloads. It will also survive bookmarking, being shared as a link, etc.

This is a good usecase for e.g. search queries, like the “search for lists to connect” input in the form above.

The most common ways to set the path and query parameters are links’ href attributes and forms’ action attributes.

You can also use a form with the method="GET" attribute which will put all input values of that form into the query string.

Lastly, you can use submit buttons’ formaction attributes to change the path and query parameters for a form only when a specific submit button is pressed. This one is super handy; I only learned about it while writing this post!

Detecting Which Button Was Pressed

One thing that surprised me when building the new form for ties was that you have no control over what the enter button does; It just submits the first submit button of the form associated to the input. An accessible form is a keyboard-controllable one, so I ended up splitting the bookmark editing form into two, so hitting enter in the list search input would not rename the bookmark.

To know which button a user pressed when submitting a form, you can use a buttons formaction attribute to change the path the form will submit to; Or you can set the name and value attributes of the button to send a specific form value to the server when that button was pressed.

Progressive Enhancement

To support users without JS, I find it easiest to write a whole feature without htmx first. This makes sure I don’t architect myself into a corner where I need to refactor later on when I find out something is not possible without JS.

After that’s done, I sprinkle some htmx on top to improve the experience where JS is available. In my example above, this includes “active search” where users don’t need to hit enter to see search results, and a little spinner to show when the page is waiting for a response from the server. It usually surprises me how little htmx is really needed when I follow this approach.

When testing changes later on, I disable JS using the browsers devtools, or I add a hx-disable attribute.

hx-target

I’m still figuring out the right way to scope htmx swaps. I’ve often found bugs because a too narrow part of the page was updated, leaving other parts of the page showing stale data. Fiddling with out-of-band swaps to solve this feels error prone and overly hard to maintain.

I usually end up swapping the whole page as it’s least likely to break in the future. A downside of this is that sometimes user input will get lost during the swap, but that’s rare and usually acceptable.

Further Reading

That’s it, I hope you took away something useful from this post! If you want find out whether this approach might be suitable for your next project, or if you are already using html/htmx and would like to learn more, here are some of my favorite resources.

Alexander Petros’ blog “Unplanned Obsolescence” is a goldmine, with Less htmx is more and Who’s Afraid of a Hard Page Load? being good starting points.

How I use HTMX with Go and Django + htmx patterns go into some patterns for using htmx in the backend. They translate well to other languages and ecosystems.

If not React, then what? can help you decide if server-rendered html and/or htmx is a good fit for the project you’re building.

Resilient Web Design is an incredibly well-written book about designing robust websites. It’s more about design in the holistic, rather than the visual sense, and has influenced lots of my technological choices. If you read one thing from this list, make it this.

Plain Vanilla Web is a reference for modern capabilities of the browser you might not be aware of, presented as a cookbook for common use-cases.

联系我们 contact @ memedata.com