Page MenuHomePhabricator

Add basic select list (dropdown) component to WVUI
Closed, ResolvedPublic

Description

Existing components

MediaWiki community:

External libraries:

Wikimedia Design Style Guide links:

Potential implementations

Select vs. Dropdown

Different libraries have different interpretations of what "select" and "dropdown" mean:

LibrarySelectDropdown
OOUI"Select" is used throughout OOUI to mean anything that involves selecting an option out of a list. SelectWidget is a generic list of options. User interaction with options is handled here, as well as methods for the developer to do things like find or select an option. Various widgets like ButtonSelectWidget, RadioSelectWidget, DropdownWidget, and ComboboxWidget are ultimately based on and expand upon the SelectWidget.DropdownWidget contains a MenuSelectWidget (which extends SelectWidget) and is basically a prefab way to build a themed, featureful substitute for <select>
Wikimedia Style Guiden/aThemed <select> substitute
Wikitn/aThemed <select> substitute
MediaSearchThemed <select> substituten/a
ContentTranslationComboboxThemed <select> substitute
VuetifyThemed <select> substituten/a
BuefyThemed <select>Menu of options, like for navigation

Some general research shows that "select" often means a <select> or similar component that shows the currently selected item and can be used in forms, while "dropdown" often means a menu with options that can be chosen (like for navigation).

Options list and other shared UI and behavior

As detailed above, in OOUI, many widgets are based on the functionality provided by the SelectWidget and others. In terms of UI, the menu of options used by the DropdownWidget appears in many other widgets (ButtonSelectWidget, ComboboxWidget, MenuTagMultiselectWidget, LookupElement, and more). Although we're moving away from the inheritance pattern in favor of composition, there are still opportunities to share code via reusable components and mixins.

The following Vue libraries implement some form of reusable or shared code:

  • Wikit: separate component for OptionsMenu that handles things like keyboard navigation, scrolling, and styles
  • MediaSearch: separate component for SelectMenu, used by the Select and AutocompleteSearchInput components, for displaying a menu of options (but interactivity is handled in the parent)
  • Vuetify: Select uses a SelectList component, which uses generic List and ListItem components
Options

Options can be passed into the component either as a prop or as slot content:

  • Wikit: An array of objects of type MenuItem (label, description, and optional tag) is passed into Dropdown as a prop, which is then passed to OptionsMenu
  • ContentTranslation/MediaSearch (which shamelessly stole this from CT): An array (of values or objects) or an object is passed into Dropdown/Select as a prop. The data is then parsed into a consistent format so it can be displayed. If items take the form of objects, they must have label and value properties.
  • Vuetify: an array of values or objects is passed into VSelect as a prop. If items are objects, you must use the default format (with text and value properties) or provide the equivalent property names so the Select component knows what to do with the data. Presumably either the VSelect or VList has to deal with the different formats the data could take, but I haven't looked that closely yet.
  • Buefy: Since the Select component implements <select>, options are passed in as actual <option> elements in the main slot
HTML elements

As noted above, Buefy is the only library that uses the native <select> element. All others build custom markup and must reimplement some of the <select> spec. We're currently unable to consistently render a custom-styled <select> across browsers, so this isn't even an option for us.

Event Timeline

AnneT renamed this task from Add dropdown/select component to WVUI to Add basic select list component to WVUI.Apr 20 2021, 8:54 PM
AnneT updated the task description. (Show Details)

Some comments:

  • Select vs. dropdown: Are we open to changing what's represented in the style guide as "dropdown" to "select" to better match the definition of these words in other libraries and places? "Dropdown" sounds like more of a generic menu to me than a custom-built select element, which is what the style guide's dropdown component (and the OOUI DropdownWidget) essentially is. Since we no longer need to follow the pattern of inheritance where "select" is a fundamental piece of functionality and "dropdown" is one of many implementations of that, maybe we can simplify both the language and the code. Perhaps most of what was housed in the OOUI SelectWidget can belong in a Vue MenuOptions (or, more generically, OptionsList?) component, which can be used as a child component in parent components like Select, ButtonSelect, Combobox, AutocompleteSearchInput/Lookup, and TagMultiselect (and we can abstract out any other code shared by these parent components into mixins/compositions)
  • Menu: Another language note: perhaps we should try to keep the word "menu" out of all of this? To me, menu means navigation, a very specific web component, and it's overly specific for this conversation.
  • OptionsList: I think Wikit gets the implementation right here with their MenuOptions component. It makes sense to have a separate component for the list of options since it's used by so many other components, and it makes sense to handle things like keyboard navigation here.
  • Options: I usually push for using slot content with sub-components when possible, and we could create an Option component to do just that. However, if we're going to have a separate OptionsList component, we'd have to pass the slot content from the Select into the slot of the OptionsList component, which seems like an antipattern. Again, I'm a fan of Wikit's solution here: use TypeScript to create a pre-defined type for options. It means less flexibility in terms of item formatting than the ContentTranslation solution, but it seems rare that you'd pass a simple array of strings into a Select component anyway, and that flexibility makes the data tricky to handle in the code (lots of conditionals and handling each format separately)
  • Scope of this task: I think we should start by implementing the style guide's dropdown component (called Dropdown or Select), which will involve making a lot of these decisions and building subcomponents, then open separate tasks for other components that implement select + option behavior.
Catrope moved this task from To Do to Doing on the Deprecated-Design-Systems-team-board board.
Catrope moved this task from Backlog to Doing on the WVUI board.

Change 697892 had a related patch set uploaded (by Catrope; author: Catrope):

[wvui@master] [dropdown] Add Dropdown and OptionsMenu components

https://gerrit.wikimedia.org/r/697892

Some comments:

  • Select vs. dropdown: Are we open to changing what's represented in the style guide as "dropdown" to "select" to better match the definition of these words in other libraries and places?

I agree, and my proposed patch calls the specific UI component a "Dropdown", while calling the more generic menu an "OptionsMenu".

  • Menu: Another language note: perhaps we should try to keep the word "menu" out of all of this? To me, menu means navigation, a very specific web component, and it's overly specific for this conversation.

I used "menu" in the name of the internal "OptionsMenu" component, but not in the externally-facing "Dropdown" component. Is that what you were going for, or were you arguing that we should keep the word "menu" out of that name too? Should it be called OptionsList like in Wikit?

  • OptionsList: I think Wikit gets the implementation right here with their MenuOptions component. It makes sense to have a separate component for the list of options since it's used by so many other components, and it makes sense to handle things like keyboard navigation here.

I agree. Ideally this component would be used by TypeaheadSearch too.

  • Options: I usually push for using slot content with sub-components when possible, and we could create an Option component to do just that. However, if we're going to have a separate OptionsList component, we'd have to pass the slot content from the Select into the slot of the OptionsList component, which seems like an antipattern. Again, I'm a fan of Wikit's solution here: use TypeScript to create a pre-defined type for options. It means less flexibility in terms of item formatting than the ContentTranslation solution, but it seems rare that you'd pass a simple array of strings into a Select component anyway, and that flexibility makes the data tricky to handle in the code (lots of conditionals and handling each format separately)

What I'm proposing in my patch is to allow customization of how options are rendered through a named slot (and, through another named slot, how the currently-selected option is rendered), but with a sensible default implementation that just does {{ item.label }}. I also created a simple TypeScript type for options that just has ID, label and disabled, but nothing else for now. We could add things like group headers and icons to this, so that those (somewhat) common cases are easy to use. But for more advanced use cases, callers would use these slots to provide their own custom rendering.

  • Scope of this task: I think we should start by implementing the style guide's dropdown component (called Dropdown or Select), which will involve making a lot of these decisions and building subcomponents, then open separate tasks for other components that implement select + option behavior.

Agreed. In particular, I left out multi-value selects. OptionsMenu should probably have a multi-value mode in the same component, but something like TagMultiSelect should probably be a separate component from Dropdown.

Here are some other things I thought about and/or would like reviewers of my patch to provide input on:

  • What do you think of my use of slots? Should OptionsMenu have a default implementation for its slot, or can it afford not to have one, since it's an internal component? What do you think of the use of named slots with default behavior in Dropdown?
  • Right now I have only one type for options (OptionMenuItem). Should there instead be a base type (OptionMenuItem) that only has what it needs (ID and disabled; label is not needed if there is no default rendering, or if the default rendering is the ID), and a specific type (DropdownItem) that extends that and adds dropdown-specific things (e.g. group header, icon)? Or are those things not really dropdown-specific and should they be supported in all components that use OptionMenu?
  • The MediaSearch implementation accepts various shorthand formats for the items prop. Should we do that / add that here too, and normalize to the longhand format internally?
  • What do you think of my use of v-model with custom prop and event names? Should I rename to modelValue/input so that I can use the modelWrapper composable? Or should the composable support alternative prop/event names? Should the composable use generics so that it can be used with typed props?
  • Should there be styling for the default label that is shown when nothing is selected (e.g. italics)? (There's already a --value-selected CSS class that could be used for this, following MediaSearch's example.)
  • How should focus be handled? The Wikit and WVUI implementations allow the dropdown gain focus when clicked, which makes it easy to hide the menu on blur and to capture key events, but because they don't prevent mousedown the text in the dropdown can be selected. The OOUI implementation prevents mousedown, so there are no selection issues, but that prevents the dropdown from gaining focus (except through the keyboard), so key events and click-away events have to be captured on the document. I chose to go the former route, but maybe we should prevent the text from being selected and prevent the dropdown from visually appearing as if it's focused unless it was focused via the keyboard?
  • Which ARIA attributes are needed that I missed? I haven't added the ones from MediaSearch that rely on IDs. MediaSearch uses the name prop to build IDs, but I don't like the idea of making name a required prop (form field names also don't have to be document-unique, only unique within a form). But we could also generate random IDs at mount time and use those, if the ID-based ARIA attributes are useful.

Follow-ups I'm thinking of working on:

  • Allow the menu to be rendered in an overlay, so that it can break out of the boundaries of the element that contains the dropdown. This will be needed for dropdowns in dialogs.
  • Fix the issue where dropdowns with an empty defaultLabel (which is the default) look weird because they're so narrow, and then grow and shrink as the selected value changes. Instead, I'd like to measure the menu items and make the dropdown the width of the widest menu item, subject to a maximum width.
  • Automatically resize the menu to fit in its container (like ClippableElement in OOUI), and to expand upwards instead of downwards when there isn't enough space below it.
  • Allow the menu to be placed in an overlay div, rather than inside the dropdown div. This will be needed for dropdowns in dialogs, where the menu needs to be able to break out of the boundaries of the dialog. I have an idea that I want to play with for how components inside dialogs could automatically pick up the dialog's overlay using provide/inject.

I used "menu" in the name of the internal "OptionsMenu" component, but not in the externally-facing "Dropdown" component. Is that what you were going for, or were you arguing that we should keep the word "menu" out of that name too? Should it be called OptionsList like in Wikit?

Relevant answer: I've gone back and forth on this a lot, and I think either is fine. We already use the word "menu" in OOUI/DSG to describe the thing we're talking about, so I think OptionsMenu is okay.

Tangent: FWIW, it has taken me a while to wrap my head around exactly what the following terms mean in OOUI:

  1. Select
  2. Menu
  3. Option

I think these are my main issues:

  1. In other libraries, "select" usually means a custom-styled element that replaces <select>, but we call that thing a dropdown
  2. When I think "menu" in terms of web design, I think navigation (even though it could mean a menu of options, which is exactly the language MDN uses to describe <select>)
  3. I can't always guess what a widget does, or which one I need, based on the widget names (ButtonMenuSelectWidget vs. ButtonSelectWidget, MenuOptionWidget vs. MenuSectionOptionWidget vs. MenuSelectWidget vs. MenuTagMultiselectWidget, MultioptionWidget vs. MultiselectWidget...) They make sense now that I understand OOUI better, but I don't find them intuitive.

Reasons this might not matter much:

  1. We can probably get rid of some of these widgets since we're no longer relying on a model of inheritance
  2. We'll have a nice demo page to show developer users what each component does (I have relied heavily on the OOUI demos page during my time here...)
  3. Maybe it's just me (although we could consider chatting with some new-to-OOUI designers and developers to see what they think)

What do you think of my use of slots? Should OptionsMenu have a default implementation for its slot, or can it afford not to have one, since it's an internal component? What do you think of the use of named slots with default behavior in Dropdown?

I'm going to spend a little more time looking at this tomorrow, but at first glance this seems like a great use of named slots—it provides default behavior, which requires no configuration beyond passing in the items array, but allows for easy customization if needed. Regarding the OptionsMenu's slot's default implementation, are you asking if you should remove the default implementation that exists in the OptionsMenu component, since it's superfluous? I'm pretty sure that's the case, and if so, I think it's better to remove it because it took me a few minutes to figure out why that slot's default implementation was seemingly duplicated in Dropdown and OptionsMenu. Let me know if I'm misunderstanding this.

Right now I have only one type for options (OptionMenuItem). Should there instead be a base type (OptionMenuItem) that only has what it needs (ID and disabled; label is not needed if there is no default rendering, or if the default rendering is the ID), and a specific type (DropdownItem) that extends that and adds dropdown-specific things (e.g. group header, icon)? Or are those things not really dropdown-specific and should they be supported in all components that use OptionMenu?

I don't think they are dropdown specific, I think they're OptionsMenu specific (see ButtonMenuSelect, for example). Could be added in a future task/patch.

The MediaSearch implementation accepts various shorthand formats for the items prop. Should we do that / add that here too, and normalize to the longhand format internally?

Hmm, it'd be nice to take the onus of formatting the data off of the developer-user, but I wonder if being presented with multiple possible formats in IDE hints would end up being confusing. I could go either way on this. Also could be handled in a future task/patch.

What do you think of my use of v-model with custom prop and event names? Should I rename to modelValue/input so that I can use the modelWrapper composable? Or should the composable support alternative prop/event names? Should the composable use generics so that it can be used with typed props?

I'd recommend we make the useModelWrapper composable customizable so you can assign different names to the prop and event. Customizing the event name for different types of elements is a no-brainer, and I think it makes sense to customize the prop name per-component so we can make it clearer what the value is. It doesn't matter to the end user, I guess, but it's helpful for reading the component code.

Should there be styling for the default label that is shown when nothing is selected (e.g. italics)? (There's already a --value-selected CSS class that could be used for this, following MediaSearch's example.)

Per the DSG, I don't think so, but it would be nice to add dynamic classes for when no value is selected and when a value is selected, in case the user wants to differentiate the styles between those states. We needed that in MediaSearch to make it obvious when a filter value was active.

How should focus be handled? The Wikit and WVUI implementations allow the dropdown gain focus when clicked, which makes it easy to hide the menu on blur and to capture key events, but because they don't prevent mousedown the text in the dropdown can be selected. The OOUI implementation prevents mousedown, so there are no selection issues, but that prevents the dropdown from gaining focus (except through the keyboard), so key events and click-away events have to be captured on the document. I chose to go the former route, but maybe we should prevent the text from being selected and prevent the dropdown from visually appearing as if it's focused unless it was focused via the keyboard?

Can you help me understand the "selection issues" that OOUI prevents by preventing mousedown? I definitely think we should focus on the menu if we can to avoid listening to events on the document, but I want to make sure we're not missing something.

Which ARIA attributes are needed that I missed? I haven't added the ones from MediaSearch that rely on IDs. MediaSearch uses the name prop to build IDs, but I don't like the idea of making name a required prop (form field names also don't have to be document-unique, only unique within a form). But we could also generate random IDs at mount time and use those, if the ID-based ARIA attributes are useful.

In OOUI, the wrapper div has aria-disabled, but I'm not sure if that's really necessary.

For the combobox, see here: it'll need aria-owns which equals the ID of the listbox, plus aria-activedescendent which equals the ID of the active menu option (active as in currently being focused/hovered over). I agree that generated IDs are better than user-provided names in this case (Wikit is already doing this).

I think we can think about labels (and aria-labelledby) if/when we consider building a Field component that pairs a label with a form element.

I think these are my main issues:

  1. In other libraries, "select" usually means a custom-styled element that replaces <select>, but we call that thing a dropdown
  2. When I think "menu" in terms of web design, I think navigation (even though it could mean a menu of options, which is exactly the language MDN uses to describe <select>)
  3. I can't always guess what a widget does, or which one I need, based on the widget names (ButtonMenuSelectWidget vs. ButtonSelectWidget, MenuOptionWidget vs. MenuSectionOptionWidget vs. MenuSelectWidget vs. MenuTagMultiselectWidget, MultioptionWidget vs. MultiselectWidget...) They make sense now that I understand OOUI better, but I don't find them intuitive.

Reasons this might not matter much:

  1. We can probably get rid of some of these widgets since we're no longer relying on a model of inheritance
  2. We'll have a nice demo page to show developer users what each component does (I have relied heavily on the OOUI demos page during my time here...)
  3. Maybe it's just me (although we could consider chatting with some new-to-OOUI designers and developers to see what they think)

It's not just you, this is legitimately confusing, and I don't know what all of these widgets are either. I think we'll suffer from this problem much less in WVUI because 1) we'll be more careful to choose non-confusing names now that we've been burned by this issue, and 2) we'll need fewer components because a) we're not using inheritance and b) we're using data rather than individual components for options.

I'm going to spend a little more time looking at this tomorrow, but at first glance this seems like a great use of named slots—it provides default behavior, which requires no configuration beyond passing in the items array, but allows for easy customization if needed.

Happy to hear that! This was the first use case I thought of when I read the Vue documentation about scoped slots: passing item objects into slots, rather than using separate components for different types of items like OOUI does.

Regarding the OptionsMenu's slot's default implementation, are you asking if you should remove the default implementation that exists in the OptionsMenu component, since it's superfluous? I'm pretty sure that's the case, and if so, I think it's better to remove it because it took me a few minutes to figure out why that slot's default implementation was seemingly duplicated in Dropdown and OptionsMenu. Let me know if I'm misunderstanding this.

Yes, that's exactly what I mean. My thinking is that OptionsMenu is an internal component, so we can expect the components that wrap it to provide an implementation for that slot. Or, alternatively, we could decide that the default implementation is OptionsMenu-specific rather than Dropdown-specific (see also next comment), and put the default implementation in OptionsMenu instead so that Dropdown (and other future components) can reuse it. That would be a little more complicated, because I'm not sure how we could do that while also allowing the users of those components to override the OptionsMenu behavior. I have an idea for how that could be done, but I have to test to see if it works.

Right now I have only one type for options (OptionMenuItem). Should there instead be a base type (OptionMenuItem) that only has what it needs (ID and disabled; label is not needed if there is no default rendering, or if the default rendering is the ID), and a specific type (DropdownItem) that extends that and adds dropdown-specific things (e.g. group header, icon)? Or are those things not really dropdown-specific and should they be supported in all components that use OptionMenu?

I don't think they are dropdown specific, I think they're OptionsMenu specific (see ButtonMenuSelect, for example). Could be added in a future task/patch.

That's a good point, and makes me want to put the default implementation of rendering items in OptionsMenu rather than in each component that uses it, so that we only have to implement it once.

The MediaSearch implementation accepts various shorthand formats for the items prop. Should we do that / add that here too, and normalize to the longhand format internally?

Hmm, it'd be nice to take the onus of formatting the data off of the developer-user, but I wonder if being presented with multiple possible formats in IDE hints would end up being confusing. I could go either way on this. Also could be handled in a future task/patch.

I think it's probably worth it, but I agree we can do it as a separate follow-up patch.

What do you think of my use of v-model with custom prop and event names? Should I rename to modelValue/input so that I can use the modelWrapper composable? Or should the composable support alternative prop/event names? Should the composable use generics so that it can be used with typed props?

I'd recommend we make the useModelWrapper composable customizable so you can assign different names to the prop and event. Customizing the event name for different types of elements is a no-brainer, and I think it makes sense to customize the prop name per-component so we can make it clearer what the value is. It doesn't matter to the end user, I guess, but it's helpful for reading the component code.

OK great, happy to hear that coming from the author of useModelWrapper :)

Should there be styling for the default label that is shown when nothing is selected (e.g. italics)? (There's already a --value-selected CSS class that could be used for this, following MediaSearch's example.)

Per the DSG, I don't think so, but it would be nice to add dynamic classes for when no value is selected and when a value is selected, in case the user wants to differentiate the styles between those states. We needed that in MediaSearch to make it obvious when a filter value was active.

OK, I will add a second class for when nothing is selected, so that both states are easy to target (rather than having to use :not()).

How should focus be handled? The Wikit and WVUI implementations allow the dropdown gain focus when clicked, which makes it easy to hide the menu on blur and to capture key events, but because they don't prevent mousedown the text in the dropdown can be selected. The OOUI implementation prevents mousedown, so there are no selection issues, but that prevents the dropdown from gaining focus (except through the keyboard), so key events and click-away events have to be captured on the document. I chose to go the former route, but maybe we should prevent the text from being selected and prevent the dropdown from visually appearing as if it's focused unless it was focused via the keyboard?

Can you help me understand the "selection issues" that OOUI prevents by preventing mousedown? I definitely think we should focus on the menu if we can to avoid listening to events on the document, but I want to make sure we're not missing something.

The main issue is that clicking or dragging accidentally selects text. For example, if you don't prevent mousedown, double clicking on the dropdown selects the nearest word in the dropdown handle, triple clicking selects the whole thing, and dragging can select the items too. What I'm doing right now is preventing mousedown and then compensating for the fact that this prevents the dropdown from being focused by manually focusing it on click. That has all the behaviors I want, but it differs from both MediaSearch (embraces focus, doesn't prevent mousedown) and OOUI (prevents mousedown, works around not having focus), and it also produces a focus outline that the DSG appears to say shouldn't be there, so I wanted to see if anyone had better/different ideas.

Which ARIA attributes are needed that I missed? I haven't added the ones from MediaSearch that rely on IDs. MediaSearch uses the name prop to build IDs, but I don't like the idea of making name a required prop (form field names also don't have to be document-unique, only unique within a form). But we could also generate random IDs at mount time and use those, if the ID-based ARIA attributes are useful.

In OOUI, the wrapper div has aria-disabled, but I'm not sure if that's really necessary.

Given that Volker submitted a patch for the Radio component last week removing a duplicative aria-disabled attribute, I'm guessing we want it on only one of the wrapper div and the head div, but not both, since one is the child of the other. The head div already has all the other aria attributes, so it should probably stay there?

For the combobox, see here: it'll need aria-owns which equals the ID of the listbox, plus aria-activedescendent which equals the ID of the active menu option (active as in currently being focused/hovered over). I agree that generated IDs are better than user-provided names in this case (Wikit is already doing this).

I think we can think about labels (and aria-labelledby) if/when we consider building a Field component that pairs a label with a form element.

OK, I will implement ID generation, and only use it for aria-owns and aria-activedescendant for now.

Regarding the OptionsMenu's slot's default implementation...

Yeah, it makes sense to try to store as much as we can in the OptionsMenu component so we don't have to re-implement the default slot (or anything else) in every component that uses it.

The main issue is that clicking or dragging accidentally selects text. For example, if you don't prevent mousedown, double clicking on the dropdown selects the nearest word in the dropdown handle, triple clicking selects the whole thing, and dragging can select the items too. What I'm doing right now is preventing mousedown and then compensating for the fact that this prevents the dropdown from being focused by manually focusing it on click. That has all the behaviors I want, but it differs from both MediaSearch (embraces focus, doesn't prevent mousedown) and OOUI (prevents mousedown, works around not having focus), and it also produces a focus outline that the DSG appears to say shouldn't be there, so I wanted to see if anyone had better/different ideas.

Hmm, I'm not picking up on the difference in focus behavior between your Dropdown component and OOUI/DSG...when is there a focus outline that shouldn't be there? In OOUI I see the focus outline on click, and it remains after selecting an item.

In OOUI, the wrapper div has aria-disabled, but I'm not sure if that's really necessary.

Given that Volker submitted a patch for the Radio component last week removing a duplicative aria-disabled attribute, I'm guessing we want it on only one of the wrapper div and the head div, but not both, since one is the child of the other. The head div already has all the other aria attributes, so it should probably stay there?

+1

Regarding the OptionsMenu's slot's default implementation...

Yeah, it makes sense to try to store as much as we can in the OptionsMenu component so we don't have to re-implement the default slot (or anything else) in every component that uses it.

It turned out to be really easy. I've amended the patch to deduplicate this.

The main issue is that clicking or dragging accidentally selects text. For example, if you don't prevent mousedown, double clicking on the dropdown selects the nearest word in the dropdown handle, triple clicking selects the whole thing, and dragging can select the items too. What I'm doing right now is preventing mousedown and then compensating for the fact that this prevents the dropdown from being focused by manually focusing it on click. That has all the behaviors I want, but it differs from both MediaSearch (embraces focus, doesn't prevent mousedown) and OOUI (prevents mousedown, works around not having focus), and it also produces a focus outline that the DSG appears to say shouldn't be there, so I wanted to see if anyone had better/different ideas.

Hmm, I'm not picking up on the difference in focus behavior between your Dropdown component and OOUI/DSG...when is there a focus outline that shouldn't be there? In OOUI I see the focus outline on click, and it remains after selecting an item.

You're right. For some reason I thought OOUI dropdowns didn't get focus, but I just checked and they do. My bad.

On a side note: reading the ARIA docs, I should also implement Home/End handling, and typeahead handling (though the latter will be better off as a separate patch, and we'll probably want to add an optional property to items so they can indicate a typeahead name that differs from their label).

I found this example in the ARIA docs that seemed to more closely track how our dropdown works, so I implemented its behavior instead. The significant difference is that I'm not setting role="combobox" (because that's only supposed to be set on input[type="text"] elements) and that I'm setting aria-activedescendant on the listbox instead of on (what used to be) the combobox. I found it strange that this example doesn't link the head (button element in their case) of the dropdown to the list, so I used aria-owns the way it's used in the combobox example.

Change 698650 had a related patch set uploaded (by Catrope; author: Catrope):

[wvui@master] [dropdown] Add composable for generated IDs, use for ARIA

https://gerrit.wikimedia.org/r/698650

On the naming question “select” vs “dropdown” in task description and further by @AnneT in T280712#7133348:

In other libraries, "select" usually means a custom-styled element that replaces <select>, but we call that thing a dropdown

Let's remember HTML select came in different flavors. This one allowing multiple options from a flat framed size=4 list for example:

image.png (210×652 px, 16 KB)

The reason we ended up with “Dropdown” was a decision to reflect our own needs and realities. There's no (need for) such open, flat list in any of our interfaces the last couple of years AFAIAO and we want to name things as closely as what they are.
For open, flat lists equivalents designers use radio groups, checkbox groups or button groups/button selects.
Hence “Dropdown” is the way to go.

Volker_E renamed this task from Add basic select list component to WVUI to Add basic select list (dropdown) component to WVUI.Jun 17 2021, 7:57 AM

Please note, that OOUI also provides DropdownInputWidget (input is always a signal for a native HTML form element in the widget), that provides the themed select element as well.

@Volker_E Thanks very much for all the additional context, this is really helpful. So we'll need a follow-up task to either create a separate component akin to DropdownInputWidget, or add an optional prop to Dropdown that results in a hidden <select> element so Dropdown can be used in forms (I'd prefer the latter).

Change 697892 merged by jenkins-bot:

[wvui@master] [dropdown] Add Dropdown and OptionsMenu components

https://gerrit.wikimedia.org/r/697892

Change 698650 merged by jenkins-bot:

[wvui@master] [dropdown] Add composable for generated IDs, use for ARIA

https://gerrit.wikimedia.org/r/698650

Volker_E moved this task from Reviewing to Done on the WVUI board.

Change 715153 had a related patch set uploaded (by VolkerE; author: VolkerE):

[mediawiki/core@master] Update WVUI to v0.3.0

https://gerrit.wikimedia.org/r/715153

Change 715153 merged by jenkins-bot:

[mediawiki/core@master] Update WVUI to v0.3.0

https://gerrit.wikimedia.org/r/715153

Change 699951 had a related patch set uploaded (by VolkerE; author: Catrope):

[mediawiki/core@master] wvui: Change the 'wvui' module back to the full WVUI bundle

https://gerrit.wikimedia.org/r/699951

Change 699951 merged by jenkins-bot:

[mediawiki/core@master] wvui: Change the 'wvui' module back to the full WVUI bundle

https://gerrit.wikimedia.org/r/699951