Give buttons the action
attribute, allowing them to make HTTP requests.
Proposal 2/3 in the Triptych Proposals.
New attributes for the button:
action
- the URL to request
method
- the HTTP method to make the request with
When the action
attribute is specified, clicking the button issues an HTTP request to the specified URL.
The method
attribute can be used to change the HTTP method of the request; it defaults to GET if not specified.
When a button with the action
attribute is included inside a form, it does not submit the form.
Instead, it makes the request without submitting additional data, exactly the same as if there had been no form at all.
One of the most common user interactions on the web is logging in or out of a website. A simple POST form, with username and password inputs, can model this interaction.
Logouts, however, do not submit data. There is currently no semantic way to represent a logout button; you have to wrap it in a form. This is one way to do it:
<form action=/logout method=POST><button>Logout</button></form>
With support for button actions, we can instead model this as a lone button creating or deleting a "session" resource.<button action=/logout method=POST>Logout</button>
, which is also fine.
<button action=/session method=DELETE>Logout</button>This creates a simplifying symmetry for session management, and allows the logout action to take advantage of DELETE's idempotency.
sessions.js
file, which exports two methods: post
and delete
. That same file can contain internal helper functions that handle authentication, nicely encapsulating the session management logic and re-using many of the same resources.
Consider a simple job application webform, which uses button actions:
<form action="/apply" method="POST"> <input type="text" name="name"> <input type="email" name="email"> <textarea name="coverletter"></textarea> <button>Submit</button> <button formaction="/draft">Save Draft</button> <button action="/" method="GET">Cancel</button> </form>
The first part of the form is three inputs that the job seeker must fill out: a name, an email, and a cover letter.
The second part is three actions you can take:
you can submit the application, save a draft of it, or give up on applying entirely.
Each of these buttons interacts with the <form>
element differently!
Submit
button submits the form, sending a POST request to the /apply URL.
Save Draft
button submits the form, but its formaction
attribute changes the form's submission URL to /draft
.
Cancel
button doesn't interact with the form at all. It just navigates back to the home page.
Semantically, each of these buttons represents a reasonable form operation:
a user might choose to submit a form, save it as a draft, or cancel it entirely.
Without button actions, you can't describe the "Cancel" action correctly.formmethod
and formaction
methods, which modify the behavior of their containing form, cannot achieve the same "Cancel" experience: using them here would incorrectly navigate to the home page with the form's contents as a query parameter.
You could achieve the same behavior with a link, however, and just style it like a button.
That will be discussed further in the existing workarounds section.
Now let's say you save a draft and return to edit it. The web page looks like this:
<form action="/apply" method="POST"> <input type="text" name="name" value="Alexander Petros"> <input type="email" name="email" value="contact@example.com"> <textarea name="coverletter"> My name is Alex and my passion is enterprise software sales. </textarea> <button>Submit</button> <button formaction="/draft/123" formmethod="PUT">Save Draft</button> <button action="/draft/123" method="DELETE">Delete Draft</button> <button action="/" method="GET">Cancel</button> </form>While the inputs of this form are the same, they have been pre-filled with the current values of the draft job application.
Submit
button still submits the form.
Save Draft
button now sets the method to PUT and the URL to /draft/123, which references the specific draft being modified.
Delete Draft
button uses button actions to issue a DELETE request to /draft/123, with no body.
Cancel
button still navigates to the home page.
Once again, all of these buttons are, from the user's perspective, co-equal form controls. But only two of the buttons rely on the form data; two of them do not. Adding buttons that can perform requests independently not only removes the need to wrap them in forms, it also allows for simpler nesting within forms to achieve a complete set of form controls.
To make a request with a button, the action
attribute must be specified with a valid URL.
The method
attribute defaults to GET if not specified;
it can also be set to all supported HTTP form methods.
Buttons can issue requests if they are type=submit
, the default button type.submit
is a mildly regrettable name in this context, as it is not typically used outside the context of a form (one does not really "submit" a link navigation).
It is far more important, however, that the button actions work with the default type
, which is and always will be submit
, than it is to have a better name for that functionality.
Besides, it's far from the most confusing default name in HTML.
type=submit
(or no type specified) is capable of making requests.
Buttons that have any other type
attribute should ignore the method
and action
attributes.
Buttons with the action
attribute that are children of a form will not submit any of the form data when clicked.
If the method
attribute is specified but no action
attribute is, then the method
attribute is ignored;
the existing formmethod
attribute should be used to alter the method of the form.
As seen in
<form action=/logout method=POST><button>Logout</button></form>
This wrapping obscures the intended semantics: that's just a button that makes a request, not a form to fill out. This limitation is not shared by JavaScript, where one can listen for click events on buttons and issue HTTP requests in response; forms play no role in that process. Wrapping buttons in input-less forms is a workaround for missing functionality in HTML.
Wrapping buttons in forms also only works for POST methods. One might be tempted to do the same with a GET request, like so:
<form action=/cancel method=GET><button>Cancel</button></form>
But this results in a request to /cancel?
, with an extraneous query parameter, which is incorrect.
Nor this technique cannot be used within forms, for any method.
Consider how you might implement /draft/123/delete
.
<form action="/apply" method="POST"> <input type="text" name="name" value="Alexander Petros"> <input type="email" name="email" value="contact@example.com"> <textarea name="coverletter"> My name is Alex and my passion is enterprise software sales. </textarea> <button>Submit</button> <button formaction="/draft/123" formmethod="PUT">Save Draft</button> <button formmethod="DELETE">Delete Draft<button> <a href="/">Cancel</a> </form>
Here the formmethod
attribute has been set to DELETE so the browser (assuming DELETE is supported) will issue a DELETE request to /apply
.
Unfortunately, this request will include all of the values in the form, which is neither expected nor desired.
A second approach is to create a second form for the Delete Draft
button.
This approach suffers from the HTML specification rule that forms cannot be nested.
Therefore we would need to put the new form outside the current form and refer to it using the form
attribute:
<form action="/apply" method="POST"> <input type="text" name="name" value="Alexander Petros"> <input type="email" name="email" value="contact@example.com"> <textarea name="coverletter"> My name is Alex and my passion is enterprise software sales. </textarea> <button>Submit</button> <button formaction="/draft/123" formmethod="PUT">Save Draft</button> <button form="delete-form">Delete Draft<button> <a href="/">Cancel</a> </form> <form id="delete-form" action="/apply" method="DELETE"></form>
This gets the functionality working, but its meaning is much less clear to the HTML reader (both developers and assistive technologies) due to the loss of locality.
Note that in both examples, the Cancel
button is now a link, which will have to be styled to look like a button, or else it will look very out of place. This despite the fact that "cancellation" is an action on the current resource, rather than a navigation to another resource (more on the implications of that in the following subsection).
This limitation is endogenous to HTML, not the web platform itself: developers often eschew HTML functionality entirely by describing the UI with buttons and adding the appropriate functionality with scripting. Button actions remove this limitation, allowing HTML to model these form actions both semantically and functionally.
For buttons that would issue GET requests, it is possible to instead use links and style them like a button. This is very common:
<a href=/logout class=button>Logout</a>Visually, it is deceptively tricky to style links exactly like styled buttons. And, if you are using unstyled buttons that inherit the system button appearance from the user's OS (often the most accessible UX), then it is impossible.
Buttons and links also have different semantics. Buttons model actions which affect the current document, while hyperlinks model other documents (which the user could choose to open in the current browsing context, or a new one). Even though they both might result in GET requests, they represent different possibilities on the page.
These distinct semantics affect how browsers implement buttons and links. One example is the middle-click: when browsing with a mouse, you can middle-click a link and it will usually open in a new tab, but if you middle-click a form submission button, the form will get submitted in the same window.
Recall the
<form action="/apply" method="POST"> <!-- inputs omitted for clarity--> <button>Submit</button> <button action="/" method="GET">Cancel</button> </form>
In a multi-page website, both of these actions should be page navigations. The "submit" button performs a page navigation with form submissions. The "close" button should be able to perform a page navigation that doesn't submit a form (since it was cancelled). A button enforces that the close action always happens in this browsing context, while a hyperlink does not.
This proposal does not take a stand on exactly when a button is appropriate versus a hyperlink. It simply acknowledges that there are times when a button is the appropriate semantic, even when the desired action is a page navigation. Those semantic differences are mildly consequential in our existing web browsing paradigms; they may be more or less so in future ones.
Some might worry that allowing buttons to make GET requests could lead to confusion about when buttons are appropriate instead of hyperlinks.
If this were a blocker to adoption, button actions could forgo implementing method=GET
.
We are of the opinion that the hyperlink is the single best-understood HTML metaphor, and that it is very unlikely that authors will start defaulting to buttons where hyperlinks would be appropriate.
This also creates a confusing diversion from the form implementation, where method=GET
is the default.
Disallowing GET would require choosing a different for buttons, thereby creating unnecessary mental overhead in remembering which elements have which defaults for the same attribute.
name
and value
attributes.<button action=/members method=POST name=person value=Alex >Signup </button>
This is a reasonable thing to add, and would fit well within the existing semantics of the button, which can have values that get submitted along with their parent form.
It is left out of the main proposal, however, in order to curtail scope. Most of the use-cases for this can also be accomplished with URLs and query parameters.