html
html copied to clipboard
Toggle/cycle buttons
Some of the discussion in https://github.com/whatwg/html/issues/4180 focused on what some platforms consider to be toggle/cycle buttons, which are somewhat distinct from switches. (A demo the WebKit team published of what might be didn't help. I have attempted to correct this with https://github.com/WebKit/WebKit/commit/58cbb3a8238bdd132719d57af647d315450053d5.)
For instance, https://developer.apple.com/design/human-interface-guidelines/toggles goes into this distinction a bit for Apple platforms. Whereas switches are rather tied to on/off, toggle/cycle buttons have a wider range. Consider a typical play/pause button for instance.
It would be quite reasonable for HTML to also offer a toggle/cycle button control, which also have their own distinct "native appearance" and are exposed differently to assistive technology.
Interesting, I had prototyped a similar component a while ago: https://nudeui.com/cycle-toggle/
Presumably a native solution should use <option>
. Which makes me wonder if it should be a presentation mode on <select>
so that it can fall back to a regular <select>
if not supported.
Just recently commented on https://github.com/openui/open-ui/issues/957 with a similar idea:
Allowing a list of options inside of a button, and having the button cycle between those might be worthwhile:
<button type=toggle> <option value="play">Play</option> <option value="pause">Pause</option> </button>
Providing
<options>
allows for a computed value, an easily computed accname, a well understood transition of state, and it is webcompat (to my knowledge).We are looking at adding richer media to
<option>
for<select>
so it would be a net gain here too.Having option also allows for N states, which may either sell you on the idea or put you off it entirely π :
<button type=toggle> <option value="1">One</option> <option value="2">Two</option> <option value="3">Three<option> </button>
Definitely could be useful to have this. Iβd expect a cycle button to be for something like a play/pause button, and the βstateβ of the button is express through a name change event in the a11y tree.
Would expect different for a toggle button though, which would be more like pressing a bold button for a rich text editor, where a pressed / not pressed state would be exposed with no name change expected.
can fall back to a regular
<select>
if not supported.
I guess it depends on the specifics but I feel (just a hunch so could be very wrong) like button would be the correct base element not select?
can fall back to a regular
<select>
if not supported.I guess it depends on the specifics but I feel (just a hunch so could be very wrong) like button would be the correct base element not select?
A button provides a closer presentation but not the actual functionality of being able to select, so itβs not useful as a fallback.
To do anything with the cycle button you'd still need JavaScript (or invokers) so a button as a fallback wouldn't really impact that? I don't think you'd want this to be a form participant which is another reason why buttons are better than selects?
Invokers which could work with these also only work on buttons?
Itβd be really strange to have a select as a fallback for a play/pause button. The select would have no accessible name without a label. I dunno, the ux / functional expectation of what a select vs button represent are quite different. Iβd lean away from that idea in favor of a default that is closer to what users would expect.
A button might work for a toggle with two states, but not for a cycle with more than two states.
A button might work for a toggle with two states, but not for a cycle with more than two states.
I'd be curious what your reasoning is for this? Is it aria limitations or UX issues you envisage?
I was thinking of a checkbox, not a button, for a two-state toggle. A button would work the same for two items as for more than two items.
<input>
supports using a <datalist>
with its list
attribute, so maybe that would actually be the better base element. It also works for backwards compatibility because an <input>
with an unknown type defaults to text
, which is a valid type for the list
attribute.
This:
<label for="ice-cream-choice">Choose a flavor:</label>
<input list="ice-cream-flavors" id="ice-cream-choice" name="ice-cream-choice" type="cycle" />
<datalist id="ice-cream-flavors">
<option value="Chocolate"></option>
<option value="Coconut"></option>
<option value="Mint"></option>
<option value="Strawberry"></option>
<option value="Vanilla"></option>
</datalist>
is "valid" HTML already, so people could start using it right away while browsers work on implementing the "cycle" (or other name) type.
One thing to say, which I know might be contentious, while graceful degradation is important there are limits to what is possible and I think we should be careful not to base future API design on historical happenstance. If we can come up with something that also has a nice degradation great, if not I don't think that's awful.
Agree with Luke. Also, @Yay295's example markup of ice cream choices seems like a good example of what people should not be doing with this sort of 'toggle' button. If you have that many choices, why aren't you using just a select element? Why make someone have to click on the button 4 times to get to what they want (vanilla), and X many more times if they have an errant click and have to cycle through all the remaining choices, just to loop around to the first, and then redo the all the clicks again till they get to their choice, hopefully without error.
A toggle button that can be used to cycle between different states, 'play/pause', 'mute/unmute', 'yes/no/maybe so' - cycling between two is probably the primary use case, cycling between 3 seems reasonable for instances where there might be a mixed/indeterminate state that could occur. You go beyond that though, and I'd have to wonder if this is really the right tool for the job. E.g., if you have that many choices (beyond 3), have you just recreated a select element, but with worse UX?
I agree. There's an extremely strong case for 2, a mild case for tri-state, and the falloff beyond that is very high. One questions if it is a limitation the platform should impose.
Most convoluted counter demo ever (forgive me):
<button type=toggle onclick="this.append(Object.assign(document.createElement('option'), { textContent: this.value + 1 }))">
<option>1</option>
</button>
I'd have to double check but I think I'd need a maximum of 4 states for anything I'd consider a toggle/cycle button. For example take a microphone button you'd have 4 states ("press to unmute", "press to mute", "indeterminate state" -think loading spinner that might show during for example a permission prompt, and maybe as a stretch a permission rejected state?).
Most would probably need 2 with a 3rd something is happening state if it's an async action.
Disabled might be able to account for some of this but that's generally a bad idea.
Then you obviously have the toggle Bold example where the label doesn't change which is much closer to the aria-pressed concept.
The "something is currently happening" state is something common to other buttons too though so perhaps that says we need a separate primitive for it?
It's common to have a "busy" state on submit buttons to stop you double clicking accidentally for example.
Just something to consider I don't want to take this discussion off course though because I feel like the basic cycle idea is something we can achieve.
A good example of a frequently-encountered cycle button with more that two states is the media player repeat-all/repeat-one/no-repeat button π/π, sometimes extended to also include stop-after-current β€/β₯.
i'd tend to lean towards a 'separate thing' to communicate a loading/busy state, since i wouldn't expect someone to have to add that to their markup of actions to cycle through.
@gibson042, thanks for pointing out those examples. these are interesting because they show how even cycle can have variants in behavior.
A play/pause button for a video, the current action presented for the control represents a behavior that is not representative of the current behavior. e.g., when the button is "Play", it's because the video is currently stopped/paused. But with those cycle buttons, the currently chosen item represents what the player is doing at that moment, and not what will happen only after the button is pressed.
For these repeat/loop buttons, I can see why someone might expect a select as a fallback. where as with the play/pause, I still wouldn't. Maybe that's even more reason to leave fallbacks up to authors to polyfill, have them provide the necessary fallback (or just fallback to the script that they're already using), rather than make a choice about what the fallback should be and have that be acceptable for some cases, and awkward for others?
Just fyi, I took my example directly from https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist. All I did was add type="cycle"
to the input element.
What about <fieldset type="condensed-radio-group">
?
I believe that backwards-compatible idiomatic accessible HTML "fallback" markup for this kind of user input is a radio group.
So instead of introducing new types of button
sπ or new elements whatsoever, what about introducing new type of fieldset
?
<fieldset type="condensed-radio-group">
<legend>Pick your mood:</legend>
<input id="r0" type="radio" name="r"> <label for="r0">aaargh</label>
<input id="r1" type="radio" name="r" checked> <label for="r1">meh</label>
<input id="r2" type="radio" name="r"> <label for="r2">weee</label>
</fieldset>
This markup can be condensed with CSS alone to visually produce single button-like element without compromising accessibility (at least to a larger degree): For general amusement see this POC sandboxπ. (It is quite simple: grid with stack of labels, transparent "next" one capturing clicks, visible "previous/checked" one displaying state. Arrow keys still work for switching state. Lack of space/enter keyboard key trigger and fact that user clicks "invisible" elements there are only two drawbacks I see in this POC.)
I guess proper accessible interface would announce only the current state and the possible next (one or all) it would be switched to after activation (what is an information that is not present in the visual image), and prevent skipping states. But even this radio group mock-up feels pretty close to that, I think.
π Reminder: introducing new types of button
cannot be backwards compatible, because any "unknown" type
value would effectively fall back to "submit
" in older user agents.
π Adopted from this old SO question asking for a "Quad-state checkbox".
Okay that PoC actually rules. I laughed out loud when I focused into it and saw you'd handled that by adding arrows. ^_^
I don't think it makes sense to impose an arbitrary limit on the number of options. For one, authors may be using multiple states + JS to emulate some kind of richer interaction.
In terms of the markup, the consideration is, how high a priority is graceful degradation these days and what kind of graceful degradation do we need?
If we want something that degrades gracefully in terms of functionality:
- A
<select>
provides the functionality even in non-supporting browsers, but I agree is a very weird fallback for some of these use cases - Radio buttons, as shown in https://github.com/whatwg/html/issues/10206#issuecomment-2010351177 is an extremely verbose solution, and IMO the cost-benefit is not there. We don't want to be stuck with this much boilerplate for time immemorial.
A <button>
degrades gracefully in terms of appearance, but not sure what benefit that offers? If anything, having something that looks like it should work, but doesn't, is worse, and if we're going to employ JS to make it work, we may as well use a completely new element so we're not limited in terms of API.
"The future is longer than the past"; I would prefer to optimize for sensible markup, at the cost of a little more effort for backwards compatibility than optimizing for something that degrades nicely but is overly verbose or unintuitive.
As for picking a new element, I can see the benefit but I imagine it'd just be very similar to <button>
no? Both in appearance, focus and management. I also imagine almost anyone writing such a component today would implement it as a <button>
. @LeaVerou your <cycle-toggle>
uses a <button>
under the hood, no? Could you talk more about what you see as the disparity between what <button>
offers and what is missing that <select>
brings?
I assume a button would not gracefully degrade in terms of appearance, so it wouldn't "look like it should work", like if the markup was something like:
<button type=toggle>
<toggleoption selected>light mode
<toggleoption>dark mode
</button>
then in down-level browsers it would render as a submit button with the text "light mode dark mode", clearly broken. So, I don't think we need to worry about that as a concern.
And I agree with Keith, in most other ways this'll act most like a button - you focus to it, activate it (repeatedly) to toggle between options, and that's it. It's less like a select
, which you focus to, activate to open the picker, arrow to choose an option, then activate to close the picker.
"The future is longer than the past"; I would prefer to optimize for sensible markup, at the cost of a little more effort for backwards compatibility than optimizing for something that degrades nicely but is overly verbose or unintuitive.
ππΌ Amen to that!
To clarify, at no point did I argue that a <select>
should be used. It was always conditional on how important a functional fallback is. If it's not that important, then by all means, let's not use a <select>
!
As for picking a new element, I can see the benefit but I imagine it'd just be very similar to
<button>
no? Both in appearance, focus and management. I also imagine almost anyone writing such a component today would implement it as a<button>
. @LeaVerou your<cycle-toggle>
uses a<button>
under the hood, no? Could you talk more about what you see as the disparity between what<button>
offers and what is missing that<select>
brings?
Any existing element carries baggage that we'd have to design the API around. E.g. <button>
has existing semantics about what happens when it has a value
, and they are not consistent with how a form control behaves. E.g. submitting a form with a <button type=button name=foo value=1>
does not actually submit a foo=1
key-value pair.
We should also consider how this kind of overloading will affect the existing element API. In this case, it would mean we'd need to add things like selectedIndex
and selectedOptions
to <button>
, despite them not making any sense for the vast majority of button use cases.
Navigating these constraints would make sense if the cost-benefit was worth it, but it's unclear to me what the benefit is in using an existing element. Is it in being able to reuse <button>
styles? If so, that is a far more general problem and is being addressed by a variety of other workstreams (since it's a common issue with web components as well).
That said, there is the far simpler example of a push button, i.e. a button that behaves like a checkbox (stays pushed for "checked"), where the argument that it should piggyback on button makes a lot more sense. And if a push button should be a <button>
, isn't cycle toggle really just a more general version of a push button? (yes, Iβm arguing against my own position now π )
submitting a form with a
<button type=button name=foo value=1>
does not actually submit afoo=1
key-value pair.
Yeah only form submission buttons will be included, which I think might be beneficial here. I'm not sure we'd want toggle buttons to be part of form submission either. IMO a toggle button should have an immediate response on the page. I'd like to hear about use cases where we'd want a toggle button as part of a form submission - my hunch is those would probably be better served by <select>
.
In this case, it would mean we'd need to add things like
selectedIndex
andselectedOptions
to<button>
, despite them not making any sense for the vast majority of button use cases.
Would we though? The way I see it the <button type=toggle>
's .value
would reflect the value of the selected option, and the label would slot the option. selectedOptions
makes more sense for <select multiple>
, and selectedIndex
(and options
) feel more easily accomplished by something like qSA. Again I'd love to see some use cases for these.
Having said that (not that I'm advocating based on this) precedent does exist for a property that doesn't make sense in the vast majority of usecases, see input.valueAsDate
or audio.videoTracks
or even button.form
for type=button
.
it's unclear to me what the benefit is in using an existing element. Is it in being able to reuse
I think it's a confluence of things, the styles are a small part but all of the existing mechanics that a button has likely apply to a toggle. Web authors have already internalised the semantics of <button>
and toggles are an extension of those semantics. "My <togglebutton>
has people asking a lot of questions already answered by <button type="toggle">
" π .
And yeah, I imagine a <button type="toggle>
with 1 option is effectively a <button type="button">
.
For what it's worth, with my HTML editor hat on (and not my Chrome hat), I think new elements are much better for new controls. Using type=""
, or even worse something like type=""
plus a new attribute (#4180), creates overly-complex designs that inherit too much from the base element.
IMO, the model that we should be following is similar to <audio>
and <video>
, or <textarea>
and <input type=text>
. If they share some functionality, they can have similar markup and JS APIs. But they should not be the same element with attributes changing their behavior.
I think WebKit is pretty flexible in how we decide to address these use cases, though if there's a reasonable backwards compatibility story that would be better. Most importantly they need to be widgets rendering-wise.
I read through the comments so far, and my very quick thoughts:
- I favor a tri-state button (on / off / mixed).
- The ideas above about adding more states are really arguments about adding values, which is addressed by existing controls.
- The control containing the accName (versus adjacent in another element) maps to my experience for this in the wild (the "bold" button cited above, for example).
- The
aria-pressed
state for<button>
already supports tri-state and has platform AAPI mappings. - Many other states are additive (as in, a disabled tri-state button would still be on, off, or mixed), so those are distinct.
- Checkboxes are, IMO, good for setting a value prior to submission, while buttons are (again IMO) appropriate for doing the submission on activation (dodging some issues of committing a change on input, a WCAG risk under SC 3.2.2 On Input, right or wrong).
- I agree these are distinct from switches (though I apply the prior two bullets for choosing a button or checkbox for a
switch
role, partly for backward compat). - I agree with @scottaohara above on the play/pause scenario.