openui / open-ui

Maintain an open standard for UI and promote its adherence and adoption.
https://open-ui.org
Other
3.59k stars 191 forks source link

select: clarifying what should be used as the chosen value #1117

Open scottaohara opened 1 month ago

scottaohara commented 1 month ago

We have been talking about what the expected value / a11y exposure of the value should be if someone changes what is displayed in an option / what the accName is of an option vs what gets rendered in the selectedoption element.

There are also questions about what happens if one were to add things like aria-label to the button or selectedoption parts of the select, or adding extra content to the button part, outside of the selectedoption. Since authors might expect to be able to modify those element and have the announced value change.

To illustrate, consider the following:

https://codepen.io/scottohara/pen/mdNqxwE

<label for=s>example</label>
<select id=s>
  <button aria-label=test1>
    Extra text
    <selectedoption aria-label="test2"></selectedoption>
  </button>
  <option aria-label="tire fire">
    <svg aria-hidden=true class="tire-fire-icon"></svg>
    <div>if it burns <a href="#">go here!</a></div>
  </option>
</select>

<style>
select,
::picker(select) { appearance: base-select; }
option div { display: none; }
... /* more styles in the linked codepen */
</style>

Per the above, checking the chrome implementation (oct 24th 2024) the aria-label becomes the returned value for the select - in this case, "tire fire". From an initial a11y pov, this makes sense because when interacting with the option, this was the accName the user would have heard and what the option visually presents in the popup picker.

But there is "extra text" that is visible in the button part.

The selectedoption and the button parts each have aria-labels.

The actual text that's returned "if it burns go here!" is display none in the option that appears in the popup, but the "author" made it visible in the rendered value to collapsed select (oh and "they" added a link too.... perfect, that will be keyboard focusable, but not announced at all).

None of this is exposed in the select's collapsed state, because the name of the select is "example", the value is "tire fire" and all this extra stuff? well, up until now, no valid implementation of a select or role=combobox would have allowed for these things. There is no value computation algorithm to account for these things, and there is honestly also an argument to be made that this sort of stuff should probably be not supported at all.

This issue is to hopefully start a conversation on what to do about these things, lest it just be me soap boxing, though i know you all love that so much.

scottaohara commented 1 month ago

So with that initial post out of the way, I of course have thoughts: (though i make no claim to how good they are):

  1. selectedoption (or whatever it will be named) should arguably be treated like the slot element, in that it should be made very clear to authors that, even though global html attributes can be set on it - because 'global' - none of those should be expected to work if the element is used within a select > button. Similar with ARIA attributes. I've been thinking about this a lot due to the conversation with happening in https://github.com/w3c/aria/pull/2344
  2. There needs to be clear author expectations for the use of the "button" within a select as well. I'm starting to wonder if it's really necessary to even have that button, just like it didn't end up being necessary to have the datalist to contain the options.

    But if that can't be changed at this point, then there probably needs to be very clear expectations set that this instance of a button should not be considered the same as if someone was using a button outside of a select. Since, the button isn't even really used in the a11y tree. being a child of the combobox or popup button parent (the select element), it's more of a styling element than an actual "button". People shouldn't expect to put HTML button attributes or ARIA attributes on that and expect anything meaningful to happen. At least, that's what i think about this right now - lest a rather significant change be made to how this is exposed to the a11y tree.

  3. Again, on the topic of the button, adding text/other content within the button seems like it will be a footgun. I recall that some of the rational there was that people wanted to be able to maybe add their own chevron to the button part and use an actual SVG for that. Or adding text like "choose:" before the selectedoption being essentially throwaway text, since it will be ignored. But, as anyone can put anything into that button part - do we anticipate that? with the talk of videos, iframes, etc., those could get put into the button part - if not just the selectedoption due to their allowance in options?
  4. We probably need to define a value computation algorithm. ARIA has a plan to do this for custom ARIA elements like combobox, but it seems now there's a need for select since the value can be calculated from things that aren't even defined in HTML (e.g., aria-label).
  5. the visible value returned to the selectedoption (not necessarily content outside of the selectedoption) probably should be what the value is exposed as. I think it's really weird that if a user chooses an option in the popup, the returned value could be displayed as / announced as something else entirely - but, if someone's going to do that, all users should arguably get that weird experience, and not have a difference between what you see vs what you might hear.
josepharhar commented 3 weeks ago

Thank you for looking at this!! I'm going to close my issue in favor of this, although I listed some examples there: https://github.com/openui/open-ui/issues/1116

selectedoption (or whatever it will be named) should arguably be treated like the slot element, in that it should be made very clear to authors that, even though global html attributes can be set on it - because 'global' - none of those should be expected to work if the element is used within a select > button. Similar with ARIA attributes.

Sounds good to me. The slot element already says it accepts global attributes in the HTML spec, so I'm not sure if I can/should account for this in the HTML spec: https://html.spec.whatwg.org/multipage/scripting.html#the-slot-element

There needs to be clear author expectations for the use of the "button" within a select as well. I'm starting to wonder if it's really necessary to even have that button, just like it didn't end up being necessary to have the datalist to contain the options.

The alternative I can think of is having <div slot=button> from selectlist, which we removed in response to whatwg feedback here: https://github.com/openui/open-ui/issues/702

Perhaps for select multiple we can avoid this problem by not using/rendering a child button at all, and forcing people to build their own popovers since rendering multiple selected options in the button sounds too hard: https://github.com/openui/open-ui/issues/1102

Since, the button isn't even really used in the a11y tree.

From the rendering and focus point of view which I implemented, the button element is sort of the main thing. I started setting delegatesFocus on the select element when a child button is inserted into the select in order to make the button become the thing that gets focus.

I didn't end up setting display:contents on the select element either, so it still has a box that gets rendered, but I used UA style magic to remove borders and box decorations from the select when a button is present.

Anyway, yeah I'm not sure which of these two elements we should recommend putting accessibilty attributes etc. on.

Again, on the topic of the button, adding text/other content within the button seems like it will be a footgun. I recall that some of the rational there was that people wanted to be able to maybe add their own chevron to the button part and use an actual SVG for that. Or adding text like "choose:" before the selectedoption being essentially throwaway text, since it will be ignored. But, as anyone can put anything into that button part - do we anticipate that?

I'm not sure exactly how common it will be, but yes I think we should anticipate this and consider if/how to incorporate it into accessibility mappings.

We probably need to define a value computation algorithm. ARIA has a plan to do this for custom ARIA elements like combobox, but it seems now there's a need for select since the value can be calculated from things that aren't even defined in HTML (e.g., aria-label).

Yes I agree. How do we proceed? I'm guessing the answer to this would resolve https://github.com/openui/open-ui/issues/1116

the visible value returned to the selectedoption (not necessarily content outside of the selectedoption) probably should be what the value is exposed as. I think it's really weird that if a user chooses an option in the popup, the returned value could be displayed as / announced as something else entirely - but, if someone's going to do that, all users should arguably get that weird experience, and not have a difference between what you see vs what you might hear.

I'm guessing this is the same question - what should be announced here?

<select>
  <button>
    <selectedoption></selectedoption>
    im a button
  </button>
  <option>hello</option>
  <option label=labeltext>innertext</option>
</select>
scottaohara commented 3 weeks ago

related https://github.com/w3c/aria/issues/2368

css-meeting-bot commented 3 weeks ago

The Open UI Community Group just discussed select: clarifying what should be used as the chosen value.

The full IRC log of that discussion <gregwhitworth> scott: as I just mentioned in the chat I created an issue during last week's conversation about this and created one over in ARIA
<gregwhitworth> scott: if someone is going to do things that are in my nightmares and this is scoped to what we want to return as the value but it does trickle out into other things
<gregwhitworth> scott: in speaking with aaron leventhall the implementation of this are one in the same and they may be able to do things with the button
<gregwhitworth> scott: I am mostly leaning towards that any ARIA labels or selectcontent are being added should just be ignored as these elements don't contribute to the select element's name
<sorvell> q+
<gregwhitworth> scott: I'm leaning that we don't do anything with that but it doesn't solve for the visible text that could be added but doesn't contribute to the accname
<gregwhitworth> scott: It could be used as a hint but not the description and it will be something like "Choose"
<sarah> q+
<gregwhitworth> scott: it's a mostly ridiculous usecase and the worst thing would be to do nothing at all and the best to expose it as a hint
<masonf> More typical text might be something like "Current selection: <selectedcontent>" right?
<gregwhitworth> scott: thank you for the responses on this to this jarhar
<gregwhitworth> scott: should the name come from the option text or what's returned to the selectedcontent
<gregwhitworth> scott: the value becomes "Loading..." when that isn't actually the value at all
<gregwhitworth> scott: I'm going to propose that the majority of this will be ignored and am curious is anything that I just said is a dream or does it sound reasonable
<gregwhitworth> ack sorvell
<gregwhitworth> sorvell: devs will do a bunch of weird stuff and we should endeavor to make it obvious when it's not intended
<gregwhitworth> sorvell: but the whole point of this is to have customization
<masonf> q+
<gregwhitworth> sorvell: the baseline should say what's in the selectedcontent and you don't have to show the selectedcontent element
<gregwhitworth> sorvell: it's odd, should it say nothing? I would vote for the entire contents of the button is announced if it's possible
<gregwhitworth> ack sarah
<scott> q+ to talk about no selectedoption shown
<gregwhitworth> sarah: the usecase of not using selectedcontent is not as unusual as you would think
<gregwhitworth> sarah: for myself outside of the most basic scenario I wouldn't use selectedcontent at all
<gregwhitworth> sarah: I don't think that will be all that weird especially if it's in a framework and the text in the button should be the value
<gregwhitworth> sarah: if it's Loading.. that is what sighted users are getting too
<gregwhitworth> ack masonf
<gregwhitworth> masonf: only slight difference was React in that it will commonly managing button content and not using the selectedcontent element.
<gregwhitworth> ack scott
<Zakim> scott, you wanted to talk about no selectedoption shown
<gregwhitworth> scott: ok, that's interesting
<gregwhitworth> scott: we're anticipating that people will not use the selectedcontent at all
<gregwhitworth> q+
<jarhar> q?
<masonf> q+
<jarhar> q+
<masonf> gregwhitworth: we discussed last week and people said nobody would use the <Selectedcontent> element. Frameworks will do their own thing.
<gregwhitworth> ack gregwhitworth
<masonf> gregwhitworth: this surprised me. Maybe we shouldn't have it? We need the declarative solution.
<jarhar> i feel like we have two options for how to expose the "value" of the <select> in the accessibility tree: either find the <option> which is selected and get a value out of that, or get the text content from the author provided <button>
<gregwhitworth> scott: the reason I'm thinking of that is that it opens up that we need to determine what is in the button at all and if there is nothing in there do we return the value of the option?
<sarah> q+
<gregwhitworth> scott: if there is no value then we've created a menu button
<gregwhitworth> sarah: I have an example where we have a combobox typeable or not to enable multiselect and it shows tags above or adjacent
<gregwhitworth> sarah: the text in the button would be placeholder button is still a select but the value is going somewhere else
<gregwhitworth> sarah: you would have a label and placeholder text, there isn't empty in the button
<sarah> q-
<gregwhitworth> masonf: but you need to read something out correct?
<gregwhitworth> scott: I get the usecases, how do we communicate the selectedcontent in the button and it is getting read out and it's being moved as a sibling but it's not a chosen option.
<gregwhitworth> masonf: It's a one-time picker that is moving to another input
<gregwhitworth> scott: I'm just stuck on the current behavior where they select that option and they have no confirmation that they've chosen anything
<gregwhitworth> q+
<gregwhitworth> ack gregwhitworth
<gregwhitworth> ack masonf
<jarhar> we could also use the "name" in the accessibility tree to expose the text content in the button, and use the "value" in the accessibility tree to expose the actual <option> element which is selected
<gregwhitworth> masonf: some people won't use the selectedcontent, React is a common one but there will be people that will want to use it
<gregwhitworth> masonf: both work if you're reading out all of the content in the button
<gregwhitworth> ack jarhar
<gregwhitworth> jarhar: I want to +1 to making cases that use selectedcontent work well and plan for it and make the acc tree do something good
<gregwhitworth> jarhar: there are two different things, the option element that is selected and content that is in the button. They may be the same thing they may be different
<gregwhitworth> jarhar: the select element itself has a name and a value with role of combobox and how we map them appropriately
<gregwhitworth> jarhar: what scott just said is that whatever is in the option that is selected should be in the acc tree
<sarah> q+
<gregwhitworth> scott: the accname should not come from the value
<sarah> q-
<gregwhitworth> ack sarah
<gregwhitworth> Zakim, end topic
<Zakim> I don't understand 'end topic', gregwhitworth
<gregwhitworth> Zakim, end meeting
josepharhar commented 1 week ago

For some more thought, here is an existing example of a select element at https://x.com/settings/profile

Screenshot 2024-11-12 at 12 00 29 PM

There's a label inside the button, so the label is "Month", and the value is "February". Here is a demo I made of the same thing in customizable select, which uses aria-label to set the label to "Month": https://jsfiddle.net/jarhar/t03aw7dx/35/

aleventhal commented 1 week ago

And if you didn't want to repeat the text "month", you would give the <div class=label> element an id, and use aria-labelledby=[id].

scottaohara commented 1 week ago

i think it's important to call out that there's no reason the label needs to be in the button part though. we did purposefully remove the use of the label element within the select/its button part, since that would go against current HTML guidance for how to use the label element.

We had also resolved that anything in the button part would become part of the calculation for the select's value - so i'm not seeing how this would solve the duplicate month announcement - unless this is suggesting that repeat content would be pruned from the accessible name?

i made up the same example in another codepen, using the label element to surround the select, so as to not put the 'label' into the button. https://codepen.io/scottohara/pen/JjgxNRJ

one thing i noticed that is a problem with these demos, is that the label element should have made it so the entire visible select could have been clickable with a mouse to open the select's popup. but it doesn't seem that's working right now. I "have" to press on the button part specifically. similarly in the jsfiddle joey created, i can't click on any part of the select that isn't the button part to open the select. That seems wrong... but i'd assume will be resolved by some of the newer resolutions to treat the select as the primary element, and the button part as inert/inert-ish

scottaohara commented 1 week ago

And if you didn't want to repeat the text "month", you would give the <div class=label> element an id, and use aria-labelledby=[id].

again, unless i'm missing something here, i think if one were to do this they'd have to aria-label or aria-labelledby 'month' to be the name of the select, and then also know to aria-hidden=true the instance of the .label in the markup, so that it wouldn't take part in contributing to the value.

these might serve as two good examples of how to achieve the same visual effect, but noting CSS hoops or the ARIA hoops that one would still have to jump through, depending on the path they chose.

aleventhal commented 1 week ago

I don't think we should get the accessible value from the magic button's contents, but rather we should take the option that is selected, and use its subtree. If we do this, the author won't need to use aria-hidden=true. Basically, the value should be what was highlighted in the drop down, and not include any additional text before or after that comes in the button.

(Side note for Scott: the accessible value should not use any child buttons or links, because they should be associated with an action almost like aria-actions).

scottaohara commented 1 week ago

i understand what you're coming from. but that's the opposite of what the group previously resolved (that what is made visible in the collapsed select should be what is exposed as the value).

so, i'm fine with going that direction, but it probably means the conversation needs to be had again and people re-evaluate why they even need to put extra content into that button part. (which i still am not sold on extra content in that button part being a good idea, but i keep losing that argument)

re:

(Side note for Scott: the accessible value should not use any child buttons or links, because they should be associated with an action almost like aria-actions).

agreed, but do we have anything to mitigate for that now, without aria-actions? that was one reason i ended up thinking that getting the value from the button part might not be the worst idea, since at least there the value could be trimmed down as any invalid links/buttons would be pruned by authors setting them to display: none

josepharhar commented 1 week ago

Where was the resolution that we should use all the text in the button as the value?

scottaohara commented 1 week ago

you're right there was no declared resolution - we ran right up to the end there. i shouldn't have said "resolved" since that's a loaded term.

but from what i recall / looking back over the minutes, with comments from greg saying he expected everything in the button part to be the value, other people echoing that on the call (though not all recorded in the minutes) and the discussion about how people would want to have an empty button and thus no value exposed - i can't imagine how we would not use the content of the button as the chosen value.

We have no guardrails up for the options themselves, no way to declare what is a description vs what is the intended name/value of an option without people having to use ARIA. Which is arguably "fine". this is v1 - get something out the door and we can improve on it. But we already have demos showing the options display one thing (more content), but cloned content is then edited via display none to present a modified version of the option.

What's returned / visible is important to accurately relay when that's all someone can see/expect to hear when they're not interacting with the popup of options. But if the value is instead pulled from the chosen option, instead of what's visible in the button part - resulting in an announced value when a value is not displayed then we're providing a different experience from what people wanted in having a button with no content in it.

so again, yes... there was no formal resolution. but everything i took away from the discussion gives me impression there isn't much room to wiggle.

to put this another way, these were the points i heard:

scottaohara commented 3 days ago

after talking this over with @smhigley and @aleventhal, we were circling around the following proposal:

Value computed as follows:

  1. If aria-valuetext is present on select element, use the attribute’s value, including the empty string
  2. Else if there is a button part, calculate the value from the contents of the button part, including the empty string
  3. If the select element does not have an aria-valuetext attribute and does not contain a button part, then the value is calculated from the name of the selected option element.

Pros: Allows for flexibility in how a value will be exposed to users. Devs can add extra content to the button part to modify the returned content from the element, or devs can forgo the use of the selectedcontent element entirely and manually clone in the content they want to the button part, or return no value at all.

Cons: arbitrary content added to the button part that was not intended to be part of the value, will be. e.g., unicode characters, or missused

Additionally:

css-meeting-bot commented 3 days ago

The Open UI Community Group just discussed select: clarifying what should be used as the chosen value, and agreed to the following:

The full IRC log of that discussion <gregwhitworth> scribenick: gregwhitworth
<gregwhitworth> scottohara: Just added a comment to the issue
<gregwhitworth> scottohara: Aaron being the angel and Sarah being the devil on the other we discussed what solution would be
<gregwhitworth> scottohara: for a quick refresher we were not sure what to do when someone has a select that has content within its button part and is a sibling to the selectedcontent element but wants to be fully in charge
<gregwhitworth> scottohara: in the issue, we are proposing the following steps to acquire a value
<masonf> qq+
<gregwhitworth> scottohara: reads out proposal here: https://github.com/openui/open-ui/issues/1117#issuecomment-2492045184
<gregwhitworth> scottohara: If aria-valuetext is present on select element, use the attribute’s value, including the empty string
<gregwhitworth> scottohara: 2. Else if there is a button part, calculate the value from the contents of the button part, including the empty string
<gregwhitworth> scottohara: this was added because of the feedback that they may not use the selectedcontent element at all
<gregwhitworth> scottohara: or concat a string value so we added this in to cover those usecases. They may have a button that is empty and they want no value read out
<gregwhitworth> 3. If the select element does not have an aria-valuetext attribute and does not contain a button part, then the value is calculated from the name of the selected option element.
<gregwhitworth> scottohara: I added this in here because this is how it seems to work right now
<gregwhitworth> scottohara: there is no pruning of the text as it's just pulling in the content of the option element
<gregwhitworth> scottohara: the positive of this is that it covers the usecases discussed but have a simpler approach
<gregwhitworth> scottohara: the con of this is that using anything that is rendered within the button part is that we can have content that they don't want used as their exposed value (eg: unicode character, mistakenly putting their label in the button part vs label)
<gregwhitworth> scottohara: we should encourage people to NOT do this
<gregwhitworth> scottohara: that's the summary
<gregwhitworth> scottohara: am I missing any usecases?
<gregwhitworth> masonf: It seems reasonable and covers the usecases and it mirrors what users see if they're sighted user
<gregwhitworth> masonf: we're not talking about the value in IDL but the the accessibility value
<gregwhitworth> scottohara: correct
<gregwhitworth> ack masonf
<Zakim> masonf, you wanted to react to a previous speaker
<gregwhitworth> jarhar: I'm supportive and happy to resolve on this
<gregwhitworth> jarhar: I'm curious about what authors should do when they want to build some things
<gregwhitworth> jarhar: this valuetext thing is meant to be something they can control but...
<gregwhitworth> jarhar: I made a quick tweak to a demo and if I don't have any text in the button part and when you click on it you get more content I should add some JS to keep the ariatext value up to date what it should really be, is that correct?
<gregwhitworth> masonf: can you use alt text?
<gregwhitworth> scottohara: the aria-label could be on the SVG and that would be carried over
<gregwhitworth> scottohara: I've worked with devs where that's not possible
<gregwhitworth> scottohara: if they can't this would be another way to do this if they can't
<gregwhitworth> scottohara: you can change the name of this thing but we don't have any mechanism to change what the value is
<gregwhitworth> scottohara: I presented another example using the emoji example and the level of effort needed to be an emoji but say "smile" versus nothing or duplicate value, etc
<gregwhitworth> scottohara: it's far more complex than aria-label on the option and this could make it so that the ariatext could be copied over from the option
<gregwhitworth> q+
<gregwhitworth> scottohara: we're going to ignore the aria attributes on the button part and selectedcontent element as they don't exist as nodes in the a11y tree
<masonf> gregwhitworth: does this apply outside of select?
<gregwhitworth> ack gregwhitworth
<gregwhitworth> scottohara: yeah, there is another issue in ARIA to use this outside of this and we'll discuss that again
<masonf> scottohara: yeah, there's another issue in ARIA to expand the role of valuetext.
<gregwhitworth> scottohara: we wanted to make sure to not block this
<gregwhitworth> Proposed Resolution: Value computed as follows: 1. ) If aria-valuetext is present on select element, use the attribute’s value, including the empty string 2.) Else if there is a button part, calculate the value from the contents of the button part, including the empty string 3.) If the select element does not have an aria-valuetext attribute and does not contain a button part, then the value is calculated from the name of the selected
<gregwhitworth> option element.
<gregwhitworth> aaron: what was the usecase for #2?
<gregwhitworth> sarah: say you have something for multi-select and you select multiple values that are displayed as tags that are outside of the button that toggles the popup and you don't want to go into the dropdown and find it
<gregwhitworth> sarah: now that I'm saying that, we normally clear the examples
<gregwhitworth> scottohara: in that same kind of examples there are a bunch of examples across Microsoft where a button part opened a listbox of options and the button had no visible content and we needed something
<gregwhitworth> scottohara: combobox invoking element that doesn't need to have a visible text string and it will need a name but the value will be exposed elsewhere
<gregwhitworth> sarah: the "add more people" is done somewhere else and you don't want to put them into the button
<gregwhitworth> Proposed Resolution: Accessible value presented to the user is computed as follows: 1. ) If aria-valuetext is present on select element, use the attribute’s value, including the empty string 2.) Else if there is a button part, calculate the value from the contents of the button part, including the empty string 3.) If the select element does not have an aria-valuetext attribute and does not contain a button part, then the value is
<gregwhitworth> calculated from the name of the selected
<masonf> proposed resolution: adopt the behavior described in https://github.com/openui/open-ui/issues/1117#issuecomment-2492045184
<masonf> +1
<bkardell_> +1
<sarah> +1
<gregwhitworth> RESOLVED: adopt the behavior described in https://github.com/openui/open-ui/issues/1117#issuecomment-2492045184
<gregwhitworth> Zakim, end meeting
scottaohara commented 3 days ago

clarification on one use case stated in the minutes, as i was probably talking too fast / might have left out some nuance:

scottohara: in that same kind of examples there are a bunch of examples across Microsoft where a button part opened a listbox of options and the button had no visible content and we needed something

scottohara: combobox invoking element that doesn't need to have a visible text string and it will need a name but the value will be exposed elsewhere

it should read more as:

there are a bunch of examples that sarah was talking to me about, across Microsoft products, where there is a button part to a combobox with no visible text / value - and there was no desire for there to be, as the chosen values were returned to a textfield or list of values that accompanied the control to invoke the listbox popup. the button (select) still needs to be named, but the value is represented elsewhere, so it shouldn't be exposed if it's not actually present for the button part.