whatwg / html

HTML Standard
https://html.spec.whatwg.org/multipage/
Other
8k stars 2.62k forks source link

Toggle (show/hide) button #10499

Closed muan closed 2 days ago

muan commented 1 month ago

What problem are you trying to solve?

cc @domenic @mfreed7 @lukewarlow @keithamus @annevk @smaug---- @scottaohara @emilio

What solutions exist today?

None. But Invoker Commands proposal(issue) intends to solve the same issues and many more the same time.

As mentioned in my comments https://github.com/whatwg/html/issues/9625#issuecomment-2138238297 and https://github.com/whatwg/html/issues/9625#issuecomment-2140081851, the Invoker Buttons proposal from the UX point of view is a Swiss army knife, and I think that is not ideal for accessibility and provides unclear semantics.

To recap, those things are:

  1. Show/hide toggle: popover/<dialog>
  2. Custom command: dispatch on invoke event for custom event
  3. (aspirational) Allowing the invocation of various commands
    1. Media controls like video.play()
    2. Input commands like input.stepUp()

I think each of these things should have a different solution, for that they have different accessibility and UX implications, or none at all. And I think it is unclear of they all have equal developer interest and should be solved the same way. I would argue that the Invoker proposal is sacrificing explicitness/clear semantic for convenience (solving all at once).

Each of the above features I think are different enough to be evaluated, implemented, and shipped separately. The proposed invoker solution is a catch all, which is prone to compatibility issues in the accessibility realm. I think this issue is clear if we were to imagine how a MDN article or AAM spec were to be written for it.

Further more, Invoker solution requires a developer to know about methods that exists on an HTML element to write out the HTML command, that is unlike other HTML element, where to construct a functional <form>, one does not need to know about form has checkValidity() or submit(), etc.

How would you solve it?

Grand scheme

I want to propose adding a semantic HTML equivalent in Accessibility API Mappings for states that are frequently used in UI patterns.

Currently semantic HTML elements are mapped to ARIA roles, I want HTML to also have a first class support for ARIA states (controlled by HTML, not developers, without JavaScript).

For example, currently (Safari):

Here I quote first rule of ARIA:

If you can use a native HTML element or attribute with the semantics and behavior you require already built in, ..., then do so.

Currently developer cannot do that because they are not supported.

Explicit markup for user interactions, semantics of the elements, and 1-1 Accessibility API Mappings/parity are the core propositions for this proposal.

Toggle button

Please refer to Toggle Button for the detailed proposal.

Imagine instead of

which already violated the first rule of ARIA, developer will need to also know to toggle the states whenever the controlled element changes... However, with this proposal, developers can use HTML alone to achieve this, never touching ARIA or JavaScript:

And aria-expanded and aria-pressed happen to be the two interactive states that will continue to be supported and do not have an HTML equivalent since there is currently no way to change these states without JavaScript.

In comparison, it is unclear that <button commandfor=idref command=commandname> will be mapped to button (expandable, default) unless developer explicitly knows and understands el.togglePopover()/dialog.showModal().

In the same vein, <summary> is another unideal case in my opinion. it shows/hides, with no spec'ed ARIA role nor AT interoperability, and changes ARIA state implicitly as the name "summary" does not suggest interaction nor changes between expand/collapse states.

How is this different from Invokers?

-- Toggle button code Invoker code Semantic/UX
<dialog> <button type=toggle for=idref> <button commandfor=idref command=showModal> toggle
<* popover> <button type=toggle for=idref> <button commandfor=idref command=togglePopover> toggle
<* togglable> <button type=toggle for=idref> N/A toggle
invoker events N/A <button commandfor=idref command=command-event> ?
media control N/A <button commandfor=idref command=toggleMuted> ?
media control N/A <button commandfor=idref command=play> ?
input control N/A <button commandfor=idref command=stepUp> ?

Anything else?

This is very similar Toggle Button Proposal by @lukewarlo, however the name toggle is used here for how/hide because following that <details>, the only current show/hide mechanism in HTML spec has.

lukewarlow commented 1 month ago

This is very similar https://github.com/openui/open-ui/issues/1058, however the name toggle is used here for how/hide because following that

, the only current show/hide mechanism in HTML spec has.

Fwiw I'm planning to rename that type, probably to type="press"

Though I think type="toggle" should be avoided entirely the term is far too overloaded (imo). Take the below example, you've got type="toggle" and then an ARIA toggle concept not matching to each other.

image

I'm a bit confused by the last two points here, is there a typo?

image

requires a developer to know about methods that exists on an HTML element

This isn't quite true, commands are generally designed to map to functions but don't have to, for example a details element has no toggle or open/show function, so in that case commands would toggle the attribute.

One big benefit of the command design over the type="toggle" design is that we can have more nuanced commands added even for something as relatively simple and understood as a dialog there's nuance.

Firstly you could (I'm talking about API design here rather than the specifics of the proposal today) have a way to show a dialog choosing between modal and non-modal (command="show" vs command="show-modal"). But on top of that you could have a way to "cancel" a dialog, rather than immediately close it, potentially via a command="request-close" See https://github.com/whatwg/html/issues/10164 for an idea of adding this capability to JS too.

You lose this nuance if you've just got a type="toggle".

button[type=toggle]:expanded { ... } button[type=toggle]:collapsed { ... }

I do think exposing the expanded state as a CSS selector is a good idea and I would advocate that we do this regardless of solution. Though probably doesn't need collapsed because we can do :not(expanded). I believe there is a previous discussion somewhere on what this could look like, with a few ideas.

In comparison, it is unclear that

As mentioned before I don't personally buy that <button type="toggle"> is any more obvious than <button command="toggle-popover"> in demonstrating that accessibility states are handled. No other existing <button type=""> has inherent accessibility semantics (that I'm aware of I could well be wrong), so it would be something developers need to learn regardless. Much like they already do for <button popovertargetaction="toggle">

muan commented 1 month ago

I'm a bit confused by the last two points here, is there a typo?

Yes, sorry. The two images should be the same. I've fixed it.

This isn't quite true, commands are generally designed to map to functions but don't have to for example a details element has no toggle or open/show function, so in that case commands would toggle the attribute.

Sure. But I think that's can be a can of worms and have not yet been discussed as part of the Invoker spec, no? I do think this potential affects how the initial methods are mapped. The divergence between some being mapped to the exact method others not, means somewhere a dictionary is to be kept and explained why they aren't matched.

I feel like it's a bit unclear as to how finished is the Invoker proposals, since I have heard a few times that all that's left is naming bike shed, since the implementation has all been done. But is it? since the explainer still has the accessibility section as under construction. Perhaps I lost the source of truth somewhere?

One big benefit of the command design over the type="toggle" design is that we can have more nuanced commands added even for something as relatively simple and understood as a dialog there's nuance.

This is kind of the thing this proposal aims to avoid (by hard limiting the scope), since then you have then every command triggering different completely different behaviors. The magic depends on the combination of the command and commandfor element type.

lukewarlow commented 1 month ago

I feel like it's a bit unclear as to how finished is the Invoker proposals, since I have heard a few times that all that's left is naming bike shed, since the implementation has all been done. But is it? since the explainer still has the accessibility section as under construction. Perhaps I lost the source of truth somewhere?

Implementations are almost finished for the dialog, popover and custom actions, the Accessibility mapping is still be done but is a known quantity. We also still need to do an AAM spec PR, and will mostly base it off the existing popover one. The explainer includes these mappings but does need updating to more explicit about what's still under discussion (the future ideas are what haven't had the a11y semantics fully spelled out yet). @keithamus cc about the explainer needing an update for the a11y section. Specifically dialog probably doesn't need the aria-expanded state per discussions with Scott.

Worth being aware some of this implementation work has been put on pause until things are hashed out so both me and Keith don't spend spare time on wasted effort. But we will make sure the tests and spec are fully up-to-date before shipping this.

This is kind of the thing this proposal aims to avoid (by hard limiting the scope), since then you have then every command triggering different completely different behaviors. The magic depends on the combination of the command and commandfor element type.

Yes it limits the scope but that explicitly limits the utility and future expansibility of the API. I personally think the ability to cancel rather than close a dialog is still a fairly known quantity, it's just a subtle difference in the behaviour. Doesn't require any extra semantics because that difference is explained by the buttons label.

lukewarlow commented 1 month ago

One thing I do want to say is that I'm (probably obviously given I have a proposal for one) a big fan of the concept of an aria-pressed toggle button being native. The exact shape of mine might be different but the concept is definitely something that we need, and regardless of invokers of the type="toggle" stuff I think it's something we should persue.

PRATYAKSH15 commented 1 month ago

Yes it limits the scope

mfreed7 commented 1 month ago

As mentioned in my comments #9625 (comment) and #9625 (comment), the Invoker Buttons proposal from the UX point of view is a Swiss army knife, and I think that is not ideal for accessibility and provides unclear semantics.

So it seems from your description like the proposal in this issue is something like "explicit accessibility". I.e. it attempts to make the accessibility more visible to developers and primary in the shape of the API. I'm very afraid that this will just confuse developers further, who will then be prone to use things incorrectly.

The idea behind the popover API and the proposed invoker commands API is that they are "accessible by default". Meaning developers reach for the behavior they want, and the browser does the right thing for them in the a11y tree, based purely on the markup.

I think each of these things should have a different solution, for that they have different accessibility and UX implications, or none at all. And I think it is unclear of they all have equal developer interest and should be solved the same way. I would argue that the Invoker proposal is sacrificing explicitness/clear semantic for convenience (solving all at once). Each of the above features I think are different enough to be evaluated, implemented, and shipped separately. The proposed invoker solution is a catch all, which is prone to compatibility issues in the accessibility realm. I think this issue is clear if we were to imagine how a MDN article or AAM spec were to be written for it.

In my view, the invoker commands API can be described very simply, for all of its use cases: "By adding the command and commandfor attributes to a button, you can cause an action (a "command") to be triggered on a target element (the one pointed to by commandfor)." That doesn't seem confusing or hard to explain to developers.

That description captures literally all of the use cases. As you point out, the a11y properties will need to change based on what the target element and command are, but that's the job of the browser now. If "compatibility issues in the accessibility" pop up, then those would either be browser bugs or more likely spec bugs, and we should fix them. They should not require developers to change things.

which already violated the first rule of ARIA, developer will need to also know to toggle the states whenever the controlled element changes... However, with this proposal, developers can use HTML alone to achieve this, never touching ARIA or JavaScript:

Helo me understand this section of your proposal. Nothing (unless I missed it!) in the invoker commands API should require any ARIA attributes in order to get correct accessibility, with the notable exception of custom commands on web components. As with all web components implementations, the WC developer does indeed need to make sure ARIA is properly applied. But in the built-in elements case, no ARIA should be required, right?

I feel like it's a bit unclear as to how finished is the Invoker proposals, since I have heard a few times that all that's left is naming bike shed, since the implementation has all been done. But is it? since the explainer still has the accessibility section as under construction. Perhaps I lost the source of truth somewhere?

There is an editor-approved and two-implementer supported spec PR waiting to land, and Chrome has the feature mostly implemented behind a flag. As pointed out above, there still needs to be an AAM spec PR to resolve the exact a11y mappings, but as also pointed out, those should be relatively straightforward, following the pattern set by Popover. @scottaohara and @aleventhal to comment on the status/difficulty there.

How is this different from Invokers?

-- Toggle button code Invoker code Semantic/UX <dialog> <button type=toggle for=idref> <button commandfor=idref command=showModal> toggle <* popover> <button type=toggle for=idref> <button commandfor=idref command=togglePopover> toggle

So these two rows (dialog and popover) are the only ones explicitly being proposed in the spec PR. How does the new proposal here address the most common use cases for dialogs and popovers, which is a non-toggle-button trigger? I.e. most trigger buttons on the web that open dialogs or show popovers do not toggle their state. They behave like normal buttons. Is that case handled by the proposed API?

muan commented 2 days ago

I think I did my best to make my case in the original text already so I am not going to add more. And since #9841 is progressing and improvements to the explainer for the accessibility section is tracked in https://github.com/openui/open-ui/pull/1078 with a PR for Accessibility API Mappings coming, I am closing this out.