Don't disable buttons
gomakethings.com
gomakethings.com
It's clearly difficult if there's so many options and this generates so much discussion. It shouldn't be this hard to get a basic UI pattern right (submitting a form once with progress updates).
If it's even a little hard, it's inevitable 90% of sites will get it wrong. There's just too much stuff for devs to realistically stay on top of to blame them.
Related: the `inert` attribute has good support now for preventing users from editing/interacting with forms that are currently being submitted so you don't have to mess with overriding input events (https://developer.mozilla.org/en-US/docs/Web/HTML/Global_att...). You'll have to add it manually when the form is submitted though, remove it if the submission fails, and use something like aria-live to give progress updates (https://developer.mozilla.org/en-US/docs/Web/Accessibility/A...). You'll also need to manually test all your custom forms like this work on actual screen readers (they have inconsistent behaviour, similar to cross-browser bugs), as well as edge cases like resubmitting after a failure, when the network request fails with error feedback, and when the network request fails from a time out. Not easy.
> Specifically, inert does the following:
> Prevents the click event from being fired when the user clicks on the element.
> Prevents the focus event from being raised by preventing the element from gaining focus.
> Hides the element and its content from assistive technologies by excluding them from the accessibility tree.
An `inert` attribute is basically invisible to screenreaders, and if you have mitigations in place for that, you might as well just use `disabled`. So I advise against using `inert`, I'm not going to say it's completely useless, but I don't think there are ton of instances where it makes sense and it's easy to get wrong.
----
The real solution here is pretty obvious: `disabled` shouldn't block focus. It's not what users want in most situations, and there are easy ways to block focus from an element if it needs focus blocked. The problem is that it would probably be extremely complicated to change browser specs to implement the better behavior. Introducing another attribute that has the correct behavior would likely be easier, but it's still not going to be trivial to get that through standardization process.
But it's no great difficulty to block a button from being focusable in HTML, that's easy. We're only in this position because `disabled` blocks focus by default and offers no way to get focus back. Even manually setting a `tabindex` does nothing. And that's just bad design, clearly there are situations where disabled buttons should be able to receive focus. If not the default behavior, it should at least be possible to allow focus as an override.
I get that HTML wasn't supposed to be an application platform, but it's what we've got for now, and all we can do is work with what we have and keep pushing forwards for better standards or a better application delivery method (which is tough, since deployment as HTML is just so easy as compared to updating a rich client application).
As far as I remember, for similar behaviour you either have to add `disabled` to all the fields (then remember to revert only the `disabled` attributes you added if the form submit fails...) or wrap all the fields in a `disabled` `fieldset` tag (now you have to research if there's any quirks when you nest `fieldset`s).
Saying that, why keep the focus on the submit button? Are there no better alternatives?
That is a good question. Probably not? I could be convinced otherwise, but I'm doubtful there's an obvious universal answer. If the form has an error, I suppose we can jump to the error field. If the form is successful, maybe we remove it from the DOM entirely? But both of those are situational and I don't feel confident that there's a universal answer.
And my instinct is (I might be wrong on this) that moving focus around automatically may just be disorienting to blind/low-vision users anyway and we should be cautious about doing so.
The current situation is I think the worst of all worlds. If a developer has the presence of mind to move the focus to another element, great. But if they don't think to do so, currently `disabled` just kind of dumps the focus, and that really feels like the worst outcome. To me, graceful degradation would be "we're going to keep you focused on the element you've selected unless the developer takes active steps to move your focus elsewhere."
The accessibility shouldn't completely fall over if the developer makes an accessibility mistake, there should be serviceable defaults for pages where developers put no extra effort into accessibility.
----
And even if we can figure out a universal standard for form behavior, buttons are used outside of forms, and `disabled` affects them there as well. If we want an accessible standard for where to move focus when a form submits, that should be a browser event that gets triggered off of the form submit event. Then it would be properly overridable with `preventDefault`, it would respond correctly to submit events even if the form button is left enabled.
It shouldn't be a result of the disabled buttons being unselectable, if it's a form-specific behavior it should be `form` behavior, not `button` behavior.
----
> To clarify, I only meant `inert` is another (fairly new) tool that can be useful, not a complete solution. For example, you can use it to prevent the user editing the fields of the form while the current field values are being submitted. You don't need to use it on the submit button.
Fair, but I personally would advise against this (although I'm open to other feedback if people have it) -- I don't think removing the form from the accessibility tree is a good response to an in-progress submit. I often like to compare with the visual UX when thinking about low-vision UX. If removing the form is the best option, then why wouldn't we full-on remove the form by setting it to `display: none`? I think the reason is that we recognize that hiding content from the page in response to an in-progress submission and then reintroducing that content based on error/success messages is bad visual UX.
So my feeling is that if it's important for a sighted user to be able to still see the form, then in most cases it's also probably important for a low-vision user to be able to access the form contents (but not edit them).
Remember that screenreaders and keyboard controls will respect `display: none`. So the only time we're using `inert` is for situations where we want content to be invisible to screenreaders but not to sighted users. And I just think that's a situation we should be very cautious about, typically the only time I'm trying to hide content from screenreaders is things like icon elements, tiny elements that shouldn't be read aloud, and usually I still want those elements to have some kind of label or reference anyway.
Yep, that's standard good practice I think for all users.
> If removing the form is the best option, then why wouldn't we full-on remove the form by setting it to `display: none`? I think the reason is that we recognize that hiding content from the page in response to an in-progress submission and then reintroducing that content based on error/success messages is bad visual UX.
So a common pattern right now to indicate the form is being submitted and it's going to take a moment is to fade out the form or the entire screen a little. And if it's going to take a few seconds, show a spinner, progress bar or progress message as well. `display: none` would look very jarring like the page just crashed. On a screen reader, you would want to announce the submission happened and the live progress, which you could get with little effort by wrapping the visual progress indicator with `aria-live`.
I agree to be cautious here but I'm not seeing a problem. I'm also not seeing the reason behind keeping the focus on the submit button when you can't interact with it now.
So let me complicate this a little bit because we're not talking about an immediate focus-change. It's not that a user clicks submit and immediately they should jump to the error field, there is a period of time where the form is being submitted and that's the only thing happening.
The question is, where should the focus go during that period?
Currently, if you do error-focusing then unless you also program handling for the progress event (which we'll get to below) what happens is the focus is on the button, then it's on the document, then it's back to the error. What I'm suggesting is that's pretty jarring, and it seems like the focus really should stay on the button until the developer is ready to move it elsewhere.
And you can move the focus anywhere else you want whenever you want, that's already easy to do, but if you forget to do so and you forget to account for the focus change, then dropping the user focus and throwing it onto the document is a really bad failure state.
And even if you do have progress-bar handling, for example in your fadeout example, if an immediate removal of the form is jarring to a visual user and they need a transition to make it more obvious what happened, then I would argue the same is true for a visually-challenged user.
While your form is fading out, maybe add an aria-live message that says submission is in progress, and by the time that message has finished being read out to a screenreader, the form will probably be finished fading and then you can move the focus wherever you want (assuming there is a good place to move it) and just remove the form from the DOM normally or set it to invisible in CSS, no extra attributes needed. This ends up being basically the same outcome as what you suggest, but I don't like this idea that we're going to kick focus to the document while we're waiting to put it somewhere else, and I don't like this idea that the browser is essentially saying "unless you have somewhere else better in mind to put the focus, then tough luck there is no focus anymore."
I buy that you might have situations where it makes sense to move the focus. I don't buy that outside of those situations the best default behavior for a browser is to drop the focus entirely and move it to the document. And while I'm more open to the idea, I also still sort of don't buy that situations that require visual transitions to improve UX for sighted users are good examples of when we should move focus immediately before giving a non-sighted user any feedback or heads up of what's happening.
----
As a side note, I do want to re-emphasize again as well, buttons are used outside of forms. If we want forms to push focus to the first errored field by default whenever a submission finishes, that really ideally would be default behavior for the `form` tag, not for buttons.
Form behavior should really not be dictating how focus events get handled for disabled buttons. The question is whether there's ever a use-case for focusing a disabled button (in or outside of a form) because right now it's impossible to do so. We can always get rid of focus from a button, but we can't put it back if the browser prevents focus entirely, so unless we're completely certain that we're never going to need to focus a disabled button, I don't think it makes sense to design an attribute in a way that forces that behavior.
If I'm moving an element offscreen but not full-on hiding it, in my experience 99% of the time it's because I want it to be visually hidden but still show up for a screenreader. `inert` gives me the opposite behavior by default. I'm trying to think when I've ever wanted an element to be visually hidden from both sighted users and a screenreader, but also couldn't do that via normal CSS.
Again, open to learning more, but this still seems to me like an attribute that should be generally avoided. I can think of some use-cases; maybe I have something like a chart where there are a lot of focusable elements but where the experience is awful for keyboard or screenreaders and I want to basically declare bankruptcy and say, "no, I don't want it to work with those controls at all, it might as well not exist for those people."
But those situations seem very rare to me.
Really its good for anything where you don't want the browser to "paint" but you want to have the content fully loaded without the user having any idea until you essentially want them to
My pet peevee is how browsers; and later smartphones; normalized that interactive elements can reflow/reorganize all the time. Now, things like joining a specific WiFi, or mirropring to a specific TV, can become a game of chance if you don't let the UI settle a bit. We had this problem more or less solved before.
People abused HTTP/HTML as an application delivery platform.
Now we finally have some better positioning / sizing for applications.
It's the same as QWERTY... Not the best way, but got popular, and now we're stuck with it, trying to patch everything for the last 25 years.
Anything I missed it's because I still haven't learned it so yes, it's a crazy world.
Even just focussing the parent element would be a much much better idea than focusing the document.
Making a button look disabled without actuall disabling it sounds like a semantically bad idea.
Double submit should be prevented by other means and "disabled" is semantically different from "this action is in progress".
This problem can basically only occur for async form submits, so one can easily just alter the button and replace it with a dummy like ("submitting") in any way it seems fit, using JS.
One can also set a "submitting" CSS class for the UI state and change text accordingly (or aria-label).
For old-fashioned plain HTML form submits, the browser handles the client side of the double-submit problem already, no?
No, users can still double-click the submit button to create multiple requests. I don't see how you can solve it without JS.
You could add a hidden UUID to the form server side and only process any submission with a given UUID once. I believe something similar is commonly done for anti-CSRF purposes.
Alternatively, just rate limit users. A user making multiple comments within a few seconds of each other is probably either accidentally double-submitting or spamming. I think this is what HN does.
Not if you have a load balancer that might send the two requests to different servers. If all of your requests are idempotent (difficult to pull off, but often worth it), you might be able to just let both servers handle it. But you still have your users inadvertently hogging resources they don't need. And if your request is taking a long time, a pissed-off user could be spamming the submit button in frustration (I'm sure I've done this before).
You should absolutely prevent the 2nd submit in JavaScript, but I'll accept that disabling the button is not the best approach for that.
> I believe something similar is commonly done for anti-CSRF purposes.
That's a crypto-signed token you can use to verify you've pre-authorized the submission, not just some UUID. (You could do it with UUIDs if you keep a list, but ... don't)
I'm struggling to remember the exact details here, but I believe Drupal 7 does what I described and just keeps a big list (expiry on entries set to 6 hours) in their database. I remember having issues with it getting very large when we had issues with bad scrapers hammering our site. Part of the issue though is that Drupal tracks the entire form (in order to prevent form tampering issues) and not just the UUID, which makes the table much larger than necessary for the use case of preventing CSRF and double-submits.
https://drupal.stackexchange.com/questions/69803/cache-form-...
What about some stupid text below the button: "Your data is being submitted..."?
Seems the meat of the argument is the disabling behavior, which is really trivial to implement in JS.
When I want to submit a form and the button changes to a regular grayed-out disabled button on click, on top of that without a page reload, I'd assume something went wrong.
You need to distinguish "disabled" from "action in progress" any way, by using a bit of special casing and CSS polish for the "submitting disabled" button... in which case, why use the disabled attribute at all, instead of preventing submit in JS?
It's not harder compared to toggling attribute in JS.
> , by using a bit of special casing and CSS polish for the "submitting disabled" button..
or by simply adding a different label to the now disabled button?
> why use the disabled attribute at all
to get the same outcome in a simpler manner? But if you recreate all the pieces with in an alternative way, that's fine, don't toggle the attribute
What original fundamental feedback? The default disabled style of a form button is not what a button should change to after submit?
It is and there is a ready-made solution to handle it https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ET... Your HTTP library likely already has support for it.
E-Tags would probably not help here, because the client won't have a tag to send along with its request until after the original response returns. But the thing we're trying to prevent in the first place is multiple concurrent requests being processed.
The Idempotency-Key header[1] is probably a better fit, but that relies on the server implementing the spec, including properly dealing with multiple in-flight requests with the same header. I found this blog post[2] pretty good for exploring some of the implementation challenges.
[1]: https://www.ietf.org/archive/id/draft-ietf-httpapi-idempoten....
[2]: http://live.julik.nl/2021/12/actually-creating-a-gem-for-ide...
A double click is something people do within a split second. Within a few ms, your application is now trying let's say, to store a comment and send a notification. Even with some sort of one-time token, process A will check the token, mark it 'used', but process B may be doing the same thing, and when process B started, the token was valid then too. If stored in SQL, you can try to do something with locking the table or row, and a second process might not get to update (or you can look in to optimistic locking/versioning/etc) but it's a thorny problem to deal with when people 'double click' and two processes start within a few ms of each other.
tldr: disabling a submit button to prevent a double click scenario is by far the easiest and most pragmatic approach that stops the problem from entering "much more complex/difficult" territory. Ideally, the 'disabled' state might auto-undisable after a couple seconds, but the original request handler should be able to reset the state after processing the response.
Would be so much nicer if this was a standard attribute to add in to forms to prevent this (2 single clicks registered back to back within X ms... ignore second).
edit: finished the article, and the 'data-submitting' approach is something I also have implemented some times, and works decently well as an alternative. You're still having to prevent double submission. etags won't really help there.
Every request that comes in contains a random "idempotency token" (can be in a hidden form field) and upon reception the server checks the previous value & sets the key if it's not there (GETSET command on Redis should do it?)
The presence of the previous value indicates whether this token was handled already (if so it means duplicate request and return an error/etc), and setting the value will ensure any subsequent duplicate request will get rejected.
Different, but not contradictory. Disabling just means "user can't do that", which is semantically reasonable when you don't want the user to do that. That stays true when the ultimate cause is that they already did that.
Replacing a submit button with a dummy is wrong, it does not solve the accessibility issue where focus is lost or moves.
Yeah that's true, I was writing this from my lunch break in a hurry and this was sloppy/wrong.
What I have actually opted for so far in SPAs and other JS form submits is to show a loading indicator inside the button tag, and ignoring the function call as long as a request is pending.
Sibling comments and the article suggest equivalent solutions.
Re sync form submit:
OK, it is still possible to trigger double submits, youre right.
I'd argue that 99% of the users notice their browser's native loading indicator though. Also I have personally experienced the "do you want to submit this POST request again?" popup for spamming form submit buttons without manually reloading the page, but I'm not sure how reliable that is or what the preconditions are.
This being possible is directly caused by browser trying to appear faster by not replacing the current page until they can start rendering the next one. Like with bfcache, vendors do their best to make full page reloads feel like an SPA if the site performance allows.
Which brings us to the only sane answer: if you need to reliably prevent double submits against malicious or clueless users, you need to act on the server.
You can't de-duplicate HTTP requests with client code.
1. Accessibility tool handles common things badly
2. Accessibility nerds try (and fail) to get everyone to stop doing common thing
3. Accessibility tool handles common thing correctly
There is a lot of great stuff you can do for accessibility, but always ask yourself if it is adding more data (like aria labels) or avoiding something you legitimately understand is not going to be compatible with accessible browsing (like onClick components inside onClick components).
But things like "don't use basic HTML functionality because tools are bad" is advice that will look very silly in 5 years when the cycle continues.
I guess one more is to be very careful not to assume anything is good or bad for accessibility. Most of the people talking have no experience with it whatsoever, and have not even installed the most popular software.
We could however introduce another attribute that has the correct behavior, deprecate `disabled`, and throw warnings during ARIA validation/linting for pages that continue to use it. Call it `blocked` or `unsusable` or have if you want to be fancy, make a `status` or something with attribute with multiple values that it accepts. Whatever seems most reasonable.
But my point is we're not trapped in the world of developers needing to poorly replicate browser functionality for every single form they make; we could still have an attribute that makes it easy for developers to by-default program forms with the correct behavior.
In Javascript, adding `let` and `const` didn't require us to get rid of `var`. We didn't have to change `var` behavior to make `let` throw errors on redeclarations. There are options here for providing tools within browsers that work correctly.
Also there's standardization and coordination between browsers, but if there is a genuine need for something they tend to find a way.
The browser's focus has to be taken as the gospel truth, because that is the only thing that all these different assistance tools can agree on. There is no standard otherwise. If you want to be a smartass and develop a speech-to-text tool that does it's own focus management separate from the browser's, well now people who need custom input solution can't use your tool, because what they are typing into (browser's focus) is different from what your tool is focusing on.
From what I know about accessibility, semantics are key. If the semantics are clear, then accessibility tools can optimize their flows accordingly. Using styling to convey semantics is a bad idea.
If accessibility tools cannot deal well with certain situations, then those tools should be improved instead of making semantics worse.
Do we really now have to work around broken screen readers too? Fuck that.
It might seem like a trivial detail, but it's not for people who depend on keyboard navigation nor is it for any form where users might want to keep the button focused after submit. You don't have to use a screen reader to be inconvenienced here.
I've had to handle the latter case in game-related apps, but another example off the top of my head would be the obvious ability to resubmit the Reddit reply form by hitting Enter again when it responds with its "Error, try again" message. I don't see the argument for settling for the poor UX of using the disabled attribute here if you knew better and had the time/energy to polish it.
There is a level of polish where it makes sense to replace the disabled attribute with a disabled (e.g. "submitting") class which implements all the things you expect, like pointer-event changes, but is even more powerful, like being able to use it on a wider variety of elements and even container elements like `form`.
Finally, as the article points out, you still to write JS to prevent double submits even when using the disabled attribute. It's used as a quick hack rather than any sort of "submitting" semantics, so there's nothing lost recreating it with CSS.
Eh, disabling an entire form submit is easy.
Developers who manually replicate built-in browser features though and manually duplicate those features through CSS have far more opportunities to make mistakes and make inaccessible forms that don't account for keyboard focus vs click focus, different input methods, etc...
The article is correct in the sense that it doesn't matter what the correct behavior is, if you want to build an accessible site for browsers as they exist today then you have to care about this. But it's still the fault of the browsers if developers are working around browser quirks because the built-in semantics that the browser exposes provide a bad UX. Ideally, over time, those quirks should be fixed within browsers, although of course doing so on the web is very complicated.
1. The screenreader transforms the visual structure into something understandable for the human behind it
2. The human tries to make sense of it and maintain a mental model of what the screenreader is presenting to them
Semantics are key for #1, but #2 can still lose track of what's happening on a complex form. For that, it helps if the human can keep trying, and fix whatever they missed on the next pass.
It's a shame that screen-reader support sucks, but it's semantically correct.
People here are arguing about whether a form being submitted counts as actually disabled or not, but that's missing the broader point: even clearly disabled buttons should be selectable, because they might focus tooltips showing why they're disabled, or because users using a keyboard cycling through the interface shouldn't have their muscle memory interrupted by having the number of tabs required to reach a button change unexpectedly depending on the context, or to prevent errors during automation and scripting, and on and on.
Even if you believe that an "in-progress" indicator should be treated differently from "disabled", it's still bad UX that disabled buttons aren't focusable and it's still bad semantics to avoid a browser attribute just to fix that problem.
Now, you might have to avoid it anyway, because good luck getting browsers and screenreaders to all change their behavior; that would be fairly difficult to do. But... it is their fault regardless of what the rest of us on the web have to do to cover for their mistake and to make our applications accessible to people who are using these tools with their broken behaviors.
----
Mozilla writes about this exact scenario (https://developer.mozilla.org/en-US/docs/Web/Accessibility/A...):
> When needing to disable native HTML form controls, developers will need to specify the disabled attribute, as it provides all of the generally expected features of disabling a control by default. However, there can be instances where elements need to be exposed as disabled, but are still available for users to find when navigating via the Tab key. Doing so can improve their discoverability as they will not be removed from the focus order of the web page, as aria-disabled does not change the focusability of such elements, nor will the elements be dimmed by default browser styling, making them easier to read. Some examples of where this may be useful include:
> The header button element associated with non-collapsible accordion panel,
> A button which is important to keep in the page's focus order, but its action is presently unavailable - such as submitting a form,
I would argue that if you are calling an aria-attribute and ignoring the real attribute specifically because the actual attribute is stricter than the aria-attribute and has unintended side-effects, then maybe the actual attribute is not accurately representing its semantic value or its expected semantic behavior.
If screenreaders and browsers aren't using aria-disabled as an indicator to skip focus, then the clear indication from that is that disabled elements should not necessarily skip focus. If disabled elements were meant to universally be unfocusable, then aria-disabled would block focus.
Also a useful note that disabling the button with the disabled attribute isn't enough to prevent people from submitting a form. Which is useful to remember, but not actually a justification for any of this (you can just check the disabled state of the button in your submit method)
If you're going to follow this article's advice, don't add a [data-submitting] attribute; use the aria-disabled as your selector. A custom data attribute does nothing for screenreaders or blind users who have no idea now whether a given button is disabled or not.
>You can also style form elements to “appear” disabled using the [data-submitting] CSS selector…
What if you submit the form, but the request never arrives?
Then either a disabled or an unresponsive submit button are both jinxed:
You may need to reload the page, possibly losing form data, in order to reset the ability to submit.
Preventing fast double submits opens up a (small) can of worms wrt. managing state.
You want to allow double submit in case of connection failure. You may even want to automate them, or add exponential back-off.
I think the proper way is to disable the button, then set the subsequent state of the button depending on the response from the server if any.
submit => check button state and if enabled then => disable button => server response (or not, then timeout) => update button state (along with error message if any)GP by contrast was suggesting allowing someone to press submit multiple times. These are not the same discussion as I see it. My response was more to address that, and was agnostic about whether the attribute or CSS disables the button.
As for using the disabled attribute, I agree with comments that it's not semantic to simulate `disabled` with CSS when we have a perfectly good disabled attribute, and it's the responsibility in this case of the screen readers to render this situation as it is meant to be understood. I'm open to reasonable disagreement here but I think the consensus is that the article is incorrect.
As for allowing multiple submissions, I think it's probably a good idea in general to program defensively against duplicate submissions and return a 409 when in doubt. But the user experience is improved by not allowing duplicate submissions on the front end for several reasons, among them that it's confusing. A POST is not idempotent, but allowing multiple submissions treats it that way.
The article uses the wording “re-enable the button after the API responds”, but doesn’t elaborate on failure/timeout.
There’s other stuff you may want to do, like persist form data. Or if you go with the article’s recommendation of preserving the button, but make it inert, perhaps add some indication to what the request is doing.
There is nothing as a “sufficiently smart” website that altered the browser’s visual language of page loading progress. Meetup.com is doing this to me lately; some links I just don’t know if they’re broken, or my net is being slow, and I’m being impatient.
Or, they are used to finding someone who's "good with computers" to assist. That alone probably hides so much garbage UX from being known to developers.
Websites, applications, and login forms should handle those generic events rather than listening specifically to keydown events.
There's really no need to simulate typing or to touch the clipboard if you're using a browser extension; and while I haven't checked, I'd honestly be kind of surprised if any of the major password manager extensions are doing so.
Of course, if for some reason the user decides to copy and paste their password, then a change event should also be handled, not just a keydown event.
My main point is that it's not a security issue that browser extensions don't simulate a keyboard. I'll grant that some applications need to simulate a keyboard because they don't have any access to the page or application, and yeah, they have a good reason for what they're doing, but it's a rare situation.
And it is technically generally not ideal for a password manager to be doing that for websites; it does tend to make the user more vulnerable to phishing attacks. But it's fine, I see the use-case, I don't think KeepassXC is wrong to include that feature or that users are wrong to use it.
It just shouldn't be impacting how login forms are designed or what events they listen to. The users who are complaining above about login forms not listening to the correct events are most likely not being insecure and are most likely not using a clipboard when they fill in their passwords, and if the password managers they're using within their browser swapped from DOM manipulation over to simulating a keyboard, that would make those password managers less secure. Simulating a keyboard is something a secure password manager should mostly avoid unless for some reason it doesn't have access to the DOM.
Using pure-CSS and data-attributes without additional indicators doesn't help low-vision users; the only thing they'll be able to tell is that click doesn't work on some of your buttons, they won't be able to see the CSS or non-semantic attributes that say the button is disabled. If you're getting rid of browser semantics, you need to include the relevant aria semantics to compensate.
Preventing double submission of data is a thoroughly basic feature for any UI library. It should be easy and obvious. But for HTML forms, the properly accessible solution shown here involves a non-intuitive combination of JavaScript, CSS and custom data attributes on an element.
The author's suggestion of using a data attribute instead of adding a disabled class to the element and the styling of it is more of preference thing.
That said, I don't really understand why there isn't something like a "singleSubmit" attribute you can add to a HTML form to automatically disable duplicate submissions. Handling double form submits is a frequent problem – you almost never want a form to be immediately resubmitable which HTML forms are.
A much better behavior would be to have the form handle the button state automatically: once you submit the form, all submit buttons of the form are disabled until the completion the button's onclick handler, the form's onsubmit handler, and any request triggered by the form action. Once all of these are done, all submit buttons belonging to this form are automatically enabled again.
If you want to get fancy, HTML could have introduced a fifth button state for this to make it distinct from a "normal" disabled button, and maybe allowed the form to change the button content for that state (you might want the button to say "please wait" or "loading"). But really, just simple sane handling of the disabled state would have been so much better than the barebones forms we have. Make it overridable by an attribute if you really want to support the edge case of a form having intentional concurrent submissions.
The first reaction is always defensive, "HTML isn't designed for this!" — and two paragraphs later it's: "HTML should really do this though".
To defend my suggestion of a "singleSubmit" attribute – I think the form element itself which already has the ability to do some basic dynamic stuff like submit a form in various ways could reasonably include the basic requirement of not allowing said form to be submitted twice. Personally this isn't something I think is a huge issue because most of the time when we'd want to do this we're looking to build fancy dynamic form behaviour anyway and in those cases JS is going to be what you need to use for that. But there are some simple webpages out there which don't care about any of that fancy stuff and literally just want a form on a page which POSTs data to another page, and in those cases it would be nice to have the "singleSubmit" attribute I suggested.
There needs to be two kinds of forms, one that asks if you really want to re-submit, and another that allows infinite submission. The backend should handle the problem too, but this would go a long way toward a better default UX.
I assume because submitting a form takes you to a different page, where the form no longer exists.
addEventListener has a 'once' option that will only fire the event once. If you're willing to handle all the error cases, it seems like the easiest option.
Either way you'd need to validate the input in the server side anyway. What are you going to do if the user (agent) misbehaves? Reddit frequently has this problem with comments popping up lots of times because of a shaky connection, and frankly I dislike letting data quality rely on your users behaving as you expect.
Not that great of an experience when you sit there and "nothing happens".
That sounds like a browser bug to me. It's absurd behavior. The focus change is as unhelpful as is humanly possible. It's not what the developer intended, it's not what anyone possibly would intend, so why is it the default behavior?
Same for "implementing an ARIA live region"
How about no? Developers are supposed to piece together hundreds of obscure articles to figure out the right way to build an accessible form. Most won't, but if they do, they're still not confident about its implementation.
How about after 30 years the web platform provides some elements that actually do something, with sane defaults instead of millions of developers being puzzled what to do for even the most basic of tasks?
Disabling buttons that are functionally no-ops is the semantically correct thing to do.
The proposed solution (hacking together a pseudo-disabled button) will only lead to more ugly buttons that don't seem to do anything. Setting the transparency on a button does not make it look disabled, nor does it stop the click animation which would suggest the button is still activating something.
If you're going to hack around silly browser bugs like these, add a loading spinner with ARIA description "submitting your form" and put it into focus. That way you can disable buttons all you like without resorting to weird workarounds.
I go one step further with buttons in my own React UI library, and I wish all UI libraries did the same:
When a button is “disabled”, it should show why either on hover or on click.
It’s a common frustration in software, especially complex ones like Adobe Photoshop or Autodesk Fusion 360, where a button is disabled and you cannot figure out why. For example: you can’t fill because it’s a “smart layer” and you need to rasterise it. Or you can’t “join” because both CAD bodies are fixed.
The programs KNOW why the button is disabled — otherwise how would it know to disable the button! So show the user why!
This alone would save countless person-hours and reduce a lot of frustration.
Don't get me wrong but this really does not explain the full story and the post ends with basically: "If you really want to know pay for it".
I don't have anything about paying for this information but at least say it is an AD for your agency.
I don't think we should have an "ad" tag. Most "show hn" posts are basically ads for new products, and still very useful and very welcome. If you think a submission is inappropriate or dishonest, you can flag it and write your reasons in comments.
Spec reference: https://html.spec.whatwg.org/multipage/form-control-infrastr...
(It’s not written in the most obvious way as regards this detail, but it’s clearly there: if there is a default button, click on it, if you can—implied is: if you can’t, just do nothing; and if there isn’t a default button, try to just submit the form.)
And of course, if you disable the button, you could prevent duplicate form submission as well through the onsubmit event.
This improves the experience for the vast majority of users; it prevents them from overlooking something, and having to go back later and fix it. If this makes life difficult for a very few? That's probably a worthwhile tradeoff.
Anyway, if you actually care about the people using screen readers, you should be offering them a separate experience anyway. One that doesn't involve JavaScript. Bet: the author of the article hasn't ever tried to use his fancy web-forms in Lynx.
I’d argue this is actually worse UX, because in some cases (like a login form), it’s obvious, but in other cases, it might not be immediately clear to a user why the button is disabled. IMO it’s better UX to always enable the button, but provide clear messaging explaining 1) why the user couldn’t do the thing and 2) what needs to be fixed in order to do the thing
That's a very odd claim. Pretty much nobody does that, and segregating people is not generally a good idea. You need to bake accessibility into everything you do. Screen readers and javascript work well enough together.
For more info on status messages: https://www.w3.org/WAI/WCAG22/Understanding/status-messages....
Were there any other UX concerns you had with this pattern?
I think this is what state machines are for. The disabled attribute is in a sense part of the button's own state-oriented behaviours, but as mentioned it isn't really sufficient to prevent other interactions or submissions. You can prevent form submissions and/or ignore them by orchestrating form UI and interactions within a state machine, though.
It sounds complex, but it's an abstraction that can be easily reused across forms, and it's not an expensive abstraction. If you're using JS, it's a trivial addition that can provide easily tested, robust, reliable form behaviours that respect your users and your business logic.
I've wondered many times why something like <form id="my-form disabled> isn't possible.
> For a screen reader user, this is particularly jarring, as now you’re in a totally different place on the page.
This just feels like an argument to abandon a very useful, well understood, and built-in tool (disabling to preventing double form submission) in order to make things better for a small subset of users of a particular tool.
Why can't this just be the problem of screen readers? Why is the "solution" to not use or remove a widely used and well understood web standard?
I'm sure there'd be a few humdingers in there.
Why? It's basic error handling. And the author linked to another explanation about the aria-live attribute for the user informing part.
In general I do think it’s mildly irresponsible to not mention error handling at all and (taking what this article does) to show reenablement only via linear code flow that assumes success. But when it’s in an article about the problems of disabling submit buttons, then it’s even more perplexing.
Whilst a physical button may be disabled to indicate being functional-but-not-ready, in software one doesn't have to show/display them until they are ready for action. If the Form is partially valid, show a Message instead of a Button, then when it is valid, pop the Action button clearly on display.
Personally I like to leave the button enabled, check the Action can be performed (form valid etc), and notify the User of errors, progress etc. It's better to communicate with the User exactly, than imply messages - it's a form, not a puzzle or an opportunity to play physical jokes on the User (gotcha, can't press this!).
In a JS framework frontend I will disable the button on submit, so the form can't be submitted twice, because accidental double clicks happen. I will enable it after at most 6 seconds or if the response is an error response.
What is the user supposed to do to enable it?
It was so bad that when you were waiting for an SMS code, after you received it and autofilled it in iOS, their website and app would automatically submit the input, and so hitting submit on your keyboard would cause the backend to reprocess the code a second time, invalidating your login.
It was obvious someone slapped the implementation together and it was so embarrassing.
Yet, I’m still nonplussed that we keep screwing up forms.
A long time ago, it became fashionable, even recommended, to disable menu items when they could not be used.
Don’t do this. Users see the disabled menu item that they want to click on, and are left entirely without a clue of what they are supposed to do to get the menu item to work.
Instead, leave the menu item enabled. If there’s some reason you can’t complete the action, the menu item can display a message telling the user why.
I'd also say I'd have no issue with a button showing as disabled provided when you attempt to click it or hover over it, it tells you why it's not yet a permitted action. But again, might be an issue for accessibility etc.
Do not disable buttons or checkboxes or whatever unless it's very obvious how to enable them! I have seen software applications that showed disabled form controls or menu entries depending on settings that were in other screens or even compile-time settings - that is unacceptable!