-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Toggle/cycle buttons #10206
Comments
Interesting, I had prototyped a similar component a while ago: https://nudeui.com/cycle-toggle/ Presumably a native solution should use |
Just recently commented on openui/open-ui#957 with a similar idea:
|
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. |
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. |
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. |
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 |
What about
|
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 |
"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 |
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:
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 |
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:
|
No, I think this is incorrect. We already have an on/off/mixed control, in two forms even (checkbox, and checkbox-switch). This discussion is largely about a control with significant values, which don't map well to on/off - for example, light vs dark for a theme switcher. And once you have two values, examples with N values (where N is reasonably small) abound. Large numbers of values clearly should be a If we wanted to more directly address cases like a bold button, which looks like a button but is semantically an on/off checkbox, that's probably well handled by stacking more onto checkboxes - |
I'm not sure I agree with this bit there could be value in a "native" aria-pressed style button that doesn't overload checkbox. That being said I agree with the rest of your comment that a cycle button supporting n values is definitely useful. |
Yes…
…and no:
That is a binary control (and a separate discussion on whether it should be binary, given user system prefs). The values of "light" and "dark" can be mapped to the on/off state by the author. Here I agree with Scott above: "[…] the 'state' of the button is express through a name change event in the a11y tree."
I disagree. I read the arguments for this above and I think the case has not been made. At least not beyond author assumptions (some of which are equating values and states). This is not a judgment; I may simply be missing it.
It's already handled with |
I... think you're misreading Scott there, as I'm seeing them say the exact opposite of what you're arguing for here. In that comment, and in this one from the preceding issue, Scott explicitly talks about needing labels on the separate states, specifically because they're not just on/off switches. In the comment you're linking to, they say a "toggle" button might be just on/off (like a "bold" button in a text editor), but a "cycle" button (like Play/Pause on a video player, or like light/dark on a theme switcher) needs accessible labels. This thread is largely about discussing the "cycle" button case, to use that terminology. (But people have variously called it "toggle" as well.)
The simplest example given so far is a light/dark mode switcher that toggles between "light", "dark", and "system default". These are three distinct and mutually exclusive states, and all need accessible labels. |
Would there be a way for a user to move to the previous state? (useless for n = 2, but the utility increases with n from there) Would there be a way for an author to programmatically set the next(/previous) state? (I don't know if there's necessary any solid use for an FSM button, but if there are it would be a shame if this was something that almost fits but not quite, especially if this properly handles all the a11y concerns for the changing labels) |
That's likely a browser UI issue, not the concern of the specification.
Do you mean moving the widget to its next/prev state, or dynamically changing what the next state will be? In either case, sure, setting the value of the control programmatically will be possible just like for every other form control. Dynamically changing the set of options would just be a matter of mutating the options in the DOM. |
doesn't seem to me that @aardrian misread my comment. And per my comment in the other thread, what I was saying there is that there needs to be a way to overwrite the implicit 'on/off' state of a switch with author defined text - because that's how people are flubbing switches up now. The label of the switch is not changing in the use case i outlined, but rather extra text is used to accompany the visual change in state. This is different than a cycle button where the visible label of the button changes, which per my comment here, can be odd since people have implemented cycle buttons to either have the current label represent a different action that will occur when activating the button again (e.g., play/pause). Or, they're being used as awkward select/radio button stand-ins where activating the button doesn't cause the current label to occur - but rather a different action will take place that the user just has to become aware of through futzing with the control. I don't really have anything good to say about that UX. So i'll leave it at that. |
Okay, "label the on/off states" is consistent with "a cycling button with 2 labeled states", but only insofar as the abstract behavior (toggling between two states by activating the control) is shared by both. A light/dark mode switcher is not an on/off control; it would be inappropriate to reuse checkboxes for this, as it has bad submission behavior, for instance (remember, checkboxes only submit if they're "checked", so one of the states would add to the submitted value; the other would just be omitted from the form submission). If you're saying specifically that switches should be able to have their labels overridden, because people are doing things like The control being discussed in this thread is, semantically and form-behaviorally, a |
I wasn't saying that. Actually, I went out of my way to say "The label of the switch is not changing in the use case i outlined, but rather extra text is used to accompany the visual change in state." to try and draw even more attention to the fact that's not what I was saying. To hopefully mitigate further confusion so as to stop conflating what I was saying in the other thread with what's being discussed here, i made a demo - https://codepen.io/scottohara/pen/MWRGedp . Hopefully this helps / identifies that this is particular topic is out of scope for this issue.
I'll point out again that this is at odds with some use cases that were mentioned for a cycle button. Mute/unmute and play/pause. The current label/name that would be displayed for these buttons doesn't represent the current state of the media. This is why I find it problematic to try and say this is semantically equivalent to a select. If it's truly the same as a select, then to adrian's point - that already exists (and if/when select elements can be styled, arguably people could be using that instead). shrug... not really sure what else to say about this that i haven't already said. Hopefully things have been clarified. |
All right, I'm still a little confused. Please, feel free to explain things very simply to me here, I won't feel condescended to. It's better than having context you think is implicit but is missing from my model. ^_^ In the codepen you just provided, the accessible label is presumably "Theme", from the label. And it's just an on/off toggle. This is presented to the user... how? And how does the displayed text ("light" and "dark") interact with this? |
you've generally got it, per your recent comment. The name/label is "theme" (well, now the name is theme because i just added the missing ID to associate the label with the input field. oops). The state that is communicated is "on/off" to someone using a screen reader. Right now "light" and "dark" aren't exposed at all (if the switch is labelled - and the pseudo content is inconsistently exposed as the control's name if the switch lacks an accName via the label element, or aria-label, for example). That was the purpose of the other issue - to create a mechanism so that the "on" and "off" audible states could be replaced with author defined replacements, e.g., "light" and "dark", as was the visual intent of the author. Again, my own opinions on whether someone should be designing their switches this way are not represented here (though if the pseudo content text is not given more consideration for accessibility, then i will absolutely be advocating for people to not add such text, even though it is "allowed" by CSS). I merely want to have a way to make this accessible so that the aural ui can match the visual ui. That's not really possible right now in a way that's sufficient / doesn't still require someone to hear the 'on/off' state announcement. If someone were expected to use a cycle button to create this ui, then... that'd be unfortunate to then have yet another type of control that needs to be visually modified to resemble a switch (again, the label for the switch is consistent, so having the label/name become 'dark' / 'light' would then raise questions like 'well is that persistent label (theme in my example) even used then? is it appended to the accName? ignored? ' heck, the role wouldn't likely be 'switch' either, since the cycle button could have more than 2 options to cycle through. i hope that helps clarify. sorry if this is just a whole buncha more words that aren't landing right. |
Btw a good theme switcher is actually a tri-state control: Auto (OS), light, dark. Another example I ran into the other day: A music note cycle toggle where clicking cycles through the 4 most common notes (whole, half, quarter, eighth). I have also seen mood cycle toggles, which inspired the examples in https://nudeui.com/cycle-toggle/ Pretty sure I have also seen examples in the wild where clicking gets you a random option, in which case the control could have any number of options.
I think that's a pretty important usability issue. The specification can certainly make non-normative recommendations about these. |
@scottaohara Okay, so as far as I can tell, what you're describing is explicitly not what this issue thread is about, and WebKit's position afaik is that such controls shouldn't be represented with checkbox-switch. (The Apple developer guidelines that Anne cites in their initial post to this thread are fairly explicit about this - they say that switches should be used for on/off toggles in the same way checkboxes are, not as toggles between two arbitrary states. Presenting a checkbox as a switch is just better in some UI scenarios.) See Anne's bugfix for a WebKit demo that explicitly removed an example using a checkbox-switch as a light/dark toggle, because that wasn't intended to be an appropriate use of that control. So, refocusing here -
@LeaVerou, great examples!
Sorry, you should definitely be able to reach the previous state (that is, this control should cycle between the options, not get stuck at the end), but I meant we shouldn't mandate anything in the spec requiring a dedicated UI affordance for going "backwards". As long as the default activation behavior goes "forwards", UAs can do whatever to offer additional options. (For example, perhaps a right-click menu could expose the options directly.) |
That's like saying it's ok for |
I believe my proposed markup meets these requirements, no?
|
Only if there is a consensus that it is really fine for this spec to be backwards incompatible with current HTML, what sounds like a really bad idea. This structure would act like a regular submit button with funky content in any currently HTML-compliant UA, as well as in any future UA that will not have (yet) adopted this hypothetical new spec. I do not believe it makes sense for form element, since it is supposed to hold a value and be submittable. It may, however, make sense for completely new elements, think As was already said, most fitting existing element for "visual and interactive extensions" here would be What leaves us again with good old radio group. (After all, select is mappable to radio group anyway.) To address complaints that <togglecyclizator_pretty_much_acting_like_a_manual_inline_mini_carousel>
<label><input type="radio" name="r" value="0"> Steady.</label>
<label><input type="radio" name="r" value="1" checked> Ready.</label>
<label><input type="radio" name="r" value="2"> Go!</label>
</togglecyclizator_pretty_much_acting_like_a_manual_inline_mini_carousel> I really see no other option (pun intended) here. |
I think there are two main categories of use cases presented here:
I suspect most disagreements are because some people have 1 in mind, while others have 2. I'd argue that 2 is a more important use case to design around, since 1 is basically already doable with script, there is little benefit to encoding the label changing logic declaratively into the HTML. Whereas 2 has no good, accessible alternative today. It may even make sense to design separate APIs for these, e.g. 1 could be A tangentially related use case is the common "button toggle", i.e. buttons that represent boolean states by either staying pressed (true) or not (false). These are often used in a group to offer mutually exclusive selections or additive ones (e.g. a formatting toolbar with bold, italic, etc). Not sure if that's something to take into account while designing this or better left as a separate use case. |
A button toggle is basically just a checkbox though, right? |
As much as a |
I tend to think you're correct on that point, @LeaVerou - there are these two use cases and where people are disagreeing / misunderstanding each other seems to stem from one's leaning towards either of those options. Hence it would appear you're much more in option 2 camp - where as I and some others, lean more towards there being a need for option 1, and that option 2 is arguably just a different way of presenting select/radio button groups. Again, another bit of a confusion point that seems to have stemmed from this is that in your demo and the radio group POC - what would be the expected label/name of the control is not what the current chosen item is, but rather "mood" in that example, or the visually hidden legend "pick a number" in the radio POC.
behavior wise, sure, in that you're toggling between a 'pressed' vs 'not pressed' state (there are similar, but different a11y API mappings for toggle buttons than checkboxes). But generally, they don't often visually resemble checkboxes, nor would they be expected to be form submittable. Prior comments in this thread called out this as a need / could (should) be a separate proposal.
Depending on which option this proposal goes with, option 1 is not a select - it's closer to a button whose primary action changes to either undo or expand on the prior action the button provided. Option 2 is more similar to a select, with different UX (click x times to reach the option you want, rather than being able to see a listing of the available options). shrug. |
I think it was also discussed here before: Plain checkbox (or 🍏 switch) has only "on (checked)" and "off (unchecked)" states: there is no way to separately label each individual state, such as "play"/"pause", "light mode"/"dark mode", "love"/"hate", "from the stem"/"from the bottom", "under the roll"/"over the roll", "socks first"/"pants first" etc, what might be quite desirable and even beneficial for the UX/a11y. |
By
I was specifically replying to
|
I agree with all of @scottaohara's previous comments on this. I read through the comments and am currently wondering why this can't be solved with just one simple attribute on the Currently, the My only gripe with this is that we currently need to use ARIA (which is supposed to be a temporary polyfill for HTML) and then we need to use CSS to apply the pressed styles to the buttons... button[aria-pressed="true"] {
// make it look pressed
} Personally, I would first propose that browsers implement a native <button ... pressed>Bold</button> and when the button is
The accName of the button in the above example is inside the button, and it doesn't need to trigger an accName change event when the button is pressed. For cycle buttons, the button's label (and accName) does change and it does need to be conveyed to ATs. So, this may be too simplistic but maybe all we need is an attribute that tells the browser "please fire a name change event when this button's name changes"? Maybe something as simple as a I don't think we need to do more to improve the DX here. Whatever the markup looks like, we will almost always still need to write JS to make the button do whatever we want it to do (assuming it's outside a form, of course). We just need to make sure that the dynamic changes are conveyed to AT users when they happen. So, as an author, I'll just implement a toggle button, change the label/accName when the user presses it, and the browser conveys the changes to AT, which in turn convey them to the user. Ultimately, the user experience is what should be front and center. Speaking of the user experience, I do have one important usability concern with some of the suggestions in the thread: I think allowing more than 3 states for a toggle button would create a usability nightmare. If I understand correctly, users will be expected to press the button God-knows-how-many times to get to the value they want, right? So, if there are, say, seven values to cycle through, they'd have to press the button six times to get to the sixth option; and if they accidentally press it too many times, they'd have to cycle back to the beginning to get back to the value they missed. And the bigger question is: how do they know how many times they need to press the button to get to the value they need? how do they know how many options are available? and how do they even know that they can press the button several times? What does the visual affordance look like for such a toggle? If the toggle button looks like a regular button, how does the user know that there are several options available to cycle through? Most toggle button use cases that are currently used across the web are well-known patterns that the majority of users are familiar with and know how to operate. The Play/Pause button is one of those examples. Users know they can either Play or Pause a video. And by pressing that one button, they understand how it works because, like Scott mentioned earlier, the button's label indicates the current state of whatever the button is controlling. But a toggle that offers multiple options (like Lea's emotions example, for instance) works completely differently. I strongly believe that any toggle that offers more than two or three options or values is something that |
Hi, coming out of left field here and then probably tapping out since I made my points above already. Since this control will have n options, since those options (what folks are calling "states") will need to have author-supplied accNames, since folks want a button-like UI, since this control will almost definitely get used in forms, is there a reason the Open UI Essentially have a flag to disallow expansion onclick and instead move through the sequence of options. Arrows can still navigate backward and forward, making it easier to get to n+23 from n+24, and a modifier for clicks to do same. The accName will have to come from the selected option if no This does not solve the ambiguity of the value / accName conveying if that is the current "state" or the one to be selected (Play/Pause). |
If this control is meant to be only used in forms (which sounds different to me from what the issue started with) then I +1 the
Agreed. For the toggle button used outside a form, another suggestion I have for a separate element (instead of an attribute) would probably be a <buttongroup>
<button>Play</button>
<button>Pause</button>
<button>[maybe a third state?]</button>
</buttongroup> The benefits here are that the fallback will be two separate buttons for the two different actions. |
Some of the discussion in #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 WebKit/WebKit@58cbb3a.)
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.
The text was updated successfully, but these errors were encountered: